2020-02-26 09:38:06

by Pratyush Yadav

[permalink] [raw]
Subject: [PATCH v2 00/11] mtd: spi-nor: add xSPI Octal DTR support

Hi,

This series adds support for octal DTR flashes in the spi-nor framework,
and then adds hooks for the Cypress Semper flash which is an xSPI
compliant Octal DTR flash.

The Cadence QSPI controller driver is also updated to run in Octal DTR
mode.

Tested on TI J721e EVM with 1-bit ECC on the Cypress flash.

This series depends on [0]. v1 can be found at [1].

[0] https://patchwork.kernel.org/patch/11355593/
[1] https://lore.kernel.org/linux-mtd/[email protected]/

Changes in v2:
- Add DT properties "spi-rx-dtr" and "spi-tx-dtr" to allow expressing
DTR capabilities.

- Set the mode bits SPI_RX_DTR and SPI_TX_DTR when we discover the DT
properties "spi-rx-dtr" and spi-tx-dtr".

- spi_nor_cypress_octal_enable() was updating nor->params.read[] with
the intention of setting the correct number of dummy cycles. But this
function is called _after_ selecting the read so setting
nor->params.read[] will have no effect. So, update nor->read_dummy
directly.

- Fix spi_nor_spimem_check_readop() and spi_nor_spimem_check_pp()
passing nor->read_proto and nor->write_proto to
spi_nor_spimem_setup_op() instead of read->proto and pp->proto
respectively.

- Move the call to cqspi_setup_opcode_ext() inside cqspi_enable_dtr().
This avoids repeating the 'if (f_pdata->is_dtr)
cqspi_setup_opcode_ext()...` snippet multiple times.

- Call the default 'supports_op()' from cqspi_supports_mem_op(). This
makes sure the buswidth requirements are also enforced along with the
DTR requirements.

- Drop the 'is_dtr' argument from spi_check_dtr_req(). We only call it
when a phase is DTR so it is redundant.

Pratyush Yadav (11):
dt-bindings: spi: allow expressing DTR capability
spi: set mode bits for "spi-rx-dtr" and "spi-tx-dtr"
spi: spi-mem: allow specifying whether an op is DTR or not
spi: spi-mem: allow specifying a command's extension
spi: cadence-quadspi: Add support for octal DTR flashes
mtd: spi-nor: add support for DTR protocol
mtd: spi-nor: get command opcode extension type from BFPT
mtd: spi-nor: parse xSPI Profile 1.0 table
mtd: spi-nor: use dummy cycle and address width info from SFDP
mtd: spi-nor: enable octal DTR mode when possible
mtd: spi-nor: add support for Cypress Semper flash

.../bindings/spi/spi-controller.yaml | 10 +
drivers/mtd/spi-nor/spi-nor.c | 594 ++++++++++++++++--
drivers/spi/spi-cadence-quadspi.c | 247 +++++++-
drivers/spi/spi-mem.c | 46 ++
drivers/spi/spi.c | 10 +-
include/linux/mtd/spi-nor.h | 55 +-
include/linux/spi/spi-mem.h | 32 +
include/linux/spi/spi.h | 2 +
8 files changed, 891 insertions(+), 105 deletions(-)

--
2.25.0


2020-02-26 09:38:15

by Pratyush Yadav

[permalink] [raw]
Subject: [PATCH v2 03/11] spi: spi-mem: allow specifying whether an op is DTR or not

Each phase is given a separate 'is_dtr' field so mixed protocols like
4S-4D-4D can be supported.

Also add the mode bits SPI_RX_DTR and SPI_TX_DTR so controllers can
specify whether they support DTR modes or not.

Signed-off-by: Pratyush Yadav <[email protected]>
---
drivers/spi/spi-mem.c | 23 +++++++++++++++++++++++
include/linux/spi/spi-mem.h | 8 ++++++++
2 files changed, 31 insertions(+)

diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c
index e5a46f0eb93b..cb13e0878b95 100644
--- a/drivers/spi/spi-mem.c
+++ b/drivers/spi/spi-mem.c
@@ -99,6 +99,16 @@ void spi_controller_dma_unmap_mem_op_data(struct spi_controller *ctlr,
}
EXPORT_SYMBOL_GPL(spi_controller_dma_unmap_mem_op_data);

+static int spi_check_dtr_req(struct spi_mem *mem, bool tx)
+{
+ u32 mode = mem->spi->mode;
+
+ if ((tx && (mode & SPI_TX_DTR)) || (!tx && (mode & SPI_RX_DTR)))
+ return 0;
+
+ return -ENOTSUPP;
+}
+
static int spi_check_buswidth_req(struct spi_mem *mem, u8 buswidth, bool tx)
{
u32 mode = mem->spi->mode;
@@ -154,6 +164,19 @@ bool spi_mem_default_supports_op(struct spi_mem *mem,
op->data.dir == SPI_MEM_DATA_OUT))
return false;

+ if (op->cmd.is_dtr && spi_check_dtr_req(mem, true))
+ return false;
+
+ if (op->addr.is_dtr && spi_check_dtr_req(mem, true))
+ return false;
+
+ if (op->dummy.is_dtr && spi_check_dtr_req(mem, true))
+ return false;
+
+ if (op->data.dir != SPI_MEM_NO_DATA && op->data.is_dtr &&
+ spi_check_dtr_req(mem, op->data.dir == SPI_MEM_DATA_OUT))
+ return false;
+
return true;
}
EXPORT_SYMBOL_GPL(spi_mem_default_supports_op);
diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h
index af9ff2f0f1b2..4669082b4e3b 100644
--- a/include/linux/spi/spi-mem.h
+++ b/include/linux/spi/spi-mem.h
@@ -71,6 +71,7 @@ enum spi_mem_data_dir {
* struct spi_mem_op - describes a SPI memory operation
* @cmd.buswidth: number of IO lines used to transmit the command
* @cmd.opcode: operation opcode
+ * @cmd.is_dtr: whether the command opcode should be sent in DTR mode or not
* @addr.nbytes: number of address bytes to send. Can be zero if the operation
* does not need to send an address
* @addr.buswidth: number of IO lines used to transmit the address cycles
@@ -78,10 +79,13 @@ enum spi_mem_data_dir {
* Note that only @addr.nbytes are taken into account in this
* address value, so users should make sure the value fits in the
* assigned number of bytes.
+ * @addr.is_dtr: whether the address should be sent in DTR mode or not
* @dummy.nbytes: number of dummy bytes to send after an opcode or address. Can
* be zero if the operation does not require dummy bytes
* @dummy.buswidth: number of IO lanes used to transmit the dummy bytes
+ * @dummy.is_dtr: whether the dummy bytes should be sent in DTR mode or not
* @data.buswidth: number of IO lanes used to send/receive the data
+ * @data.is_dtr: whether the data should be sent in DTR mode or not
* @data.dir: direction of the transfer
* @data.nbytes: number of data bytes to send/receive. Can be zero if the
* operation does not involve transferring data
@@ -92,21 +96,25 @@ struct spi_mem_op {
struct {
u8 buswidth;
u8 opcode;
+ bool is_dtr;
} cmd;

struct {
u8 nbytes;
u8 buswidth;
u64 val;
+ bool is_dtr;
} addr;

struct {
u8 nbytes;
u8 buswidth;
+ bool is_dtr;
} dummy;

struct {
u8 buswidth;
+ bool is_dtr;
enum spi_mem_data_dir dir;
unsigned int nbytes;
union {
--
2.25.0

2020-02-26 09:38:20

by Pratyush Yadav

[permalink] [raw]
Subject: [PATCH v2 06/11] mtd: spi-nor: add support for DTR protocol

Double Transfer Rate (DTR) is SPI protocol in which data is transferred
on each clock edge as opposed to on each clock cycle. Make
framework-level changes to allow supporting flashes in DTR mode.

Right now, mixed DTR modes are not supported. So, for example a mode
like 4S-4D-4D will not work. All phases need to be either DTR or STR.

Signed-off-by: Pratyush Yadav <[email protected]>
---
drivers/mtd/spi-nor/spi-nor.c | 245 ++++++++++++++++++++++++++--------
include/linux/mtd/spi-nor.h | 34 +++--
2 files changed, 213 insertions(+), 66 deletions(-)

diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index 4fc632ec18fe..6102520550a1 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -238,6 +238,7 @@ struct flash_info {
* status register. Must be used with
* SPI_NOR_HAS_TB.
*/
+#define SPI_NOR_OCTAL_DTR_READ BIT(18) /* Flash supports octal DTR Read. */

/* Part specific fixup hooks. */
const struct spi_nor_fixups *fixups;
@@ -245,6 +246,46 @@ struct flash_info {

#define JEDEC_MFR(info) ((info)->id[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.
+ */
+static void spi_nor_spimem_setup_op(const struct spi_nor *nor,
+ struct spi_mem_op *op,
+ const enum spi_nor_protocol proto)
+{
+
+ 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.buf.in || op->data.buf.out)
+ op->data.buswidth = spi_nor_get_protocol_data_nbits(proto);
+
+ if (spi_nor_protocol_is_dtr(proto)) {
+ /*
+ * spi-mem supports mixed DTR modes, but right now we can only
+ * have all phases either DTR or STR. IOW, spi-mem can have
+ * something like 4S-4D-4D, but spi-nor can't. So, set all 4
+ * phases to either DTR or STR.
+ */
+ op->cmd.is_dtr = op->addr.is_dtr = op->dummy.is_dtr
+ = op->data.is_dtr = true;
+
+ /* 2 bytes per clock cycle in DTR mode. */
+ op->dummy.nbytes *= 2;
+
+ op->cmd.ext_type = nor->cmd_ext;
+ }
+}
+
/**
* spi_nor_spimem_xfer_data() - helper function to read/write data to
* flash's memory region
@@ -316,14 +357,12 @@ static ssize_t spi_nor_spimem_read_data(struct spi_nor *nor, loff_t from,
SPI_MEM_OP_DUMMY(nor->read_dummy, 1),
SPI_MEM_OP_DATA_IN(len, buf, 1));

- /* 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;

return spi_nor_spimem_xfer_data(nor, &op);
}
@@ -365,13 +404,11 @@ static ssize_t spi_nor_spimem_write_data(struct spi_nor *nor, loff_t to,
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_OUT(len, buf, 1));

- 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);
+
return spi_nor_spimem_xfer_data(nor, &op);
}

@@ -410,10 +447,16 @@ static int spi_nor_write_enable(struct spi_nor *nor)
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);
+ if (spi_nor_protocol_is_dtr(nor->reg_proto))
+ ret = -ENOTSUPP;
+ else
+ ret = nor->controller_ops->write_reg(nor,
+ SPINOR_OP_WREN,
+ NULL, 0);
}

if (ret)
@@ -439,10 +482,16 @@ static int spi_nor_write_disable(struct spi_nor *nor)
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);
+ if (spi_nor_protocol_is_dtr(nor->reg_proto))
+ ret = -ENOTSUPP;
+ else
+ ret = nor->controller_ops->write_reg(nor,
+ SPINOR_OP_WRDI,
+ NULL, 0);
}

if (ret)
@@ -501,10 +550,15 @@ static int spi_nor_read_fsr(struct spi_nor *nor, u8 *fsr)
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_IN(1, fsr, 1));

+ 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);
+ if (spi_nor_protocol_is_dtr(nor->reg_proto))
+ ret = -ENOTSUPP;
+ else
+ ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDFSR,
+ fsr, 1);
}

if (ret)
@@ -533,9 +587,15 @@ static int spi_nor_read_cr(struct spi_nor *nor, u8 *cr)
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_IN(1, cr, 1));

+ 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);
+ if (spi_nor_protocol_is_dtr(nor->reg_proto))
+ ret = -ENOTSUPP;
+ else
+ ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDCR,
+ cr, 1);
}

if (ret)
@@ -566,12 +626,17 @@ static int macronix_set_4byte(struct spi_nor *nor, bool enable)
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);
+ if (spi_nor_protocol_is_dtr(nor->reg_proto))
+ ret = -ENOTSUPP;
+ else
+ ret = nor->controller_ops->write_reg(nor,
+ enable ? SPINOR_OP_EN4B :
+ SPINOR_OP_EX4B,
+ NULL, 0);
}

if (ret)
@@ -624,10 +689,15 @@ static int spansion_set_4byte(struct spi_nor *nor, bool enable)
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1));

+ 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);
+ if (spi_nor_protocol_is_dtr(nor->reg_proto))
+ ret = -ENOTSUPP;
+ else
+ ret = nor->controller_ops->write_reg(nor, SPINOR_OP_BRWR,
+ nor->bouncebuf, 1);
}

if (ret)
@@ -656,10 +726,16 @@ static int spi_nor_write_ear(struct spi_nor *nor, u8 ear)
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1));

+ 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);
+ if (spi_nor_protocol_is_dtr(nor->reg_proto))
+ ret = -ENOTSUPP;
+ else
+ ret = nor->controller_ops->write_reg(nor,
+ SPINOR_OP_WREAR,
+ nor->bouncebuf, 1);
}

if (ret)
@@ -719,10 +795,16 @@ static int spi_nor_xread_sr(struct spi_nor *nor, u8 *sr)
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_IN(1, sr, 1));

+ 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);
+ if (spi_nor_protocol_is_dtr(nor->reg_proto))
+ ret = -ENOTSUPP;
+ else
+ ret = nor->controller_ops->read_reg(nor,
+ SPINOR_OP_XRDSR,
+ sr, 1);
}

if (ret)
@@ -764,10 +846,16 @@ static void spi_nor_clear_sr(struct spi_nor *nor)
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);
+ if (spi_nor_protocol_is_dtr(nor->reg_proto))
+ ret = -ENOTSUPP;
+ else
+ ret = nor->controller_ops->write_reg(nor,
+ SPINOR_OP_CLSR,
+ NULL, 0);
}

if (ret)
@@ -817,10 +905,16 @@ static void spi_nor_clear_fsr(struct spi_nor *nor)
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);
+ if (spi_nor_protocol_is_dtr(nor->reg_proto))
+ ret = -ENOTSUPP;
+ else
+ ret = nor->controller_ops->write_reg(nor,
+ SPINOR_OP_CLFSR,
+ NULL, 0);
}

if (ret)
@@ -950,10 +1044,16 @@ static int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len)
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_OUT(len, sr, 1));

+ 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);
+ if (spi_nor_protocol_is_dtr(nor->reg_proto))
+ ret = -ENOTSUPP;
+ else
+ ret = nor->controller_ops->write_reg(nor,
+ SPINOR_OP_WRSR,
+ sr, len);
}

if (ret) {
@@ -1152,10 +1252,16 @@ static int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2)
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_OUT(1, sr2, 1));

+ 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);
+ if (spi_nor_protocol_is_dtr(nor->reg_proto))
+ ret = -ENOTSUPP;
+ else
+ ret = nor->controller_ops->write_reg(nor,
+ SPINOR_OP_WRSR2,
+ sr2, 1);
}

if (ret) {
@@ -1186,10 +1292,16 @@ static int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2)
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_IN(1, sr2, 1));

+ 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);
+ if (spi_nor_protocol_is_dtr(nor->reg_proto))
+ ret = -ENOTSUPP;
+ else
+ ret = nor->controller_ops->read_reg(nor,
+ SPINOR_OP_RDSR2,
+ sr2, 1);
}

if (ret)
@@ -1217,10 +1329,16 @@ static int spi_nor_erase_chip(struct spi_nor *nor)
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);
+ if (spi_nor_protocol_is_dtr(nor->write_proto))
+ ret = -ENOTSUPP;
+ else
+ ret = nor->controller_ops->write_reg(nor,
+ SPINOR_OP_CHIP_ERASE,
+ NULL, 0);
}

if (ret)
@@ -1379,7 +1497,11 @@ static int spi_nor_erase_sector(struct spi_nor *nor, u32 addr)
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 (spi_nor_protocol_is_dtr(nor->write_proto)) {
+ return -ENOTSUPP;
} else if (nor->controller_ops->erase) {
return nor->controller_ops->erase(nor, addr);
}
@@ -3045,6 +3167,7 @@ static 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,
@@ -3061,6 +3184,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,
@@ -3184,13 +3308,11 @@ static int spi_nor_spimem_check_readop(struct spi_nor *nor,
SPI_MEM_OP_DUMMY(0, 1),
SPI_MEM_OP_DATA_IN(0, NULL, 1));

- 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);
+
return spi_nor_spimem_check_op(nor, &op);
}

@@ -3210,9 +3332,7 @@ static int spi_nor_spimem_check_pp(struct spi_nor *nor,
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_OUT(0, NULL, 1));

- 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);
}
@@ -3230,11 +3350,10 @@ 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;
+ /* 2-2-2 and 4-4-4 modes are not supported yet, mask them all. */
+ *hwcaps &= ~(SNOR_HWCAPS_READ_2_2_2 | \
+ SNOR_HWCAPS_READ_4_4_4 | \
+ SNOR_HWCAPS_PP_4_4_4);

for (cap = 0; cap < sizeof(*hwcaps) * BITS_PER_BYTE; cap++) {
int rdidx, ppidx;
@@ -4240,9 +4359,16 @@ static int spi_nor_parse_4bait(struct spi_nor *nor,
}

/* 4BAIT is the only SFDP table that indicates page program support. */
- if (pp_hwcaps & SNOR_HWCAPS_PP)
+ if (pp_hwcaps & SNOR_HWCAPS_PP) {
spi_nor_set_pp_settings(&params_pp[SNOR_CMD_PP],
SPINOR_OP_PP_4B, SNOR_PROTO_1_1_1);
+ /*
+ * Since xSPI Page Program opcode is backward compatible with
+ * Legacy SPI, use Legacy SPI opcode there as well.
+ */
+ spi_nor_set_pp_settings(&params_pp[SNOR_CMD_PP_8_8_8_DTR],
+ SPINOR_OP_PP_4B, SNOR_PROTO_8_8_8_DTR);
+ }
if (pp_hwcaps & SNOR_HWCAPS_PP_1_1_4)
spi_nor_set_pp_settings(&params_pp[SNOR_CMD_PP_1_1_4],
SPINOR_OP_PP_1_1_4_4B,
@@ -4797,6 +4923,13 @@ static void spi_nor_info_init_params(struct spi_nor *nor)
spi_nor_set_pp_settings(&params->page_programs[SNOR_CMD_PP],
SPINOR_OP_PP, SNOR_PROTO_1_1_1);

+ /*
+ * Since xSPI Page Program opcode is backward compatible with
+ * Legacy SPI, use Legacy SPI opcode there as well.
+ */
+ spi_nor_set_pp_settings(&params->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).
@@ -4924,7 +5057,8 @@ static void 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);

@@ -4984,7 +5118,9 @@ static int spi_nor_init(struct spi_nor *nor)
return err;
}

- if (nor->addr_width == 4 && !(nor->flags & SNOR_F_4B_OPCODES)) {
+ if (nor->addr_width == 4 &&
+ !(nor->info->flags & SPI_NOR_OCTAL_DTR_READ) &&
+ !(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
@@ -5038,6 +5174,9 @@ static int spi_nor_set_addr_width(struct spi_nor *nor)
{
if (nor->addr_width) {
/* already configured from SFDP */
+ } else if (spi_nor_protocol_is_dtr(nor->read_proto)) {
+ /* Always use 4-byte addresses in DTR mode. */
+ nor->addr_width = 4;
} else if (nor->info->addr_width) {
nor->addr_width = nor->info->addr_width;
} else if (nor->mtd.size > 0x1000000) {
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 5abd91cc6dfa..364f37276d78 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -195,6 +195,7 @@ enum spi_nor_protocol {
SNOR_PROTO_1_2_2_DTR = SNOR_PROTO_DTR(1, 2, 2),
SNOR_PROTO_1_4_4_DTR = SNOR_PROTO_DTR(1, 4, 4),
SNOR_PROTO_1_8_8_DTR = SNOR_PROTO_DTR(1, 8, 8),
+ SNOR_PROTO_8_8_8_DTR = SNOR_PROTO_DTR(8, 8, 8),
};

static inline bool spi_nor_protocol_is_dtr(enum spi_nor_protocol proto)
@@ -345,7 +346,7 @@ struct spi_nor_hwcaps {
* then Quad SPI protocols before Dual SPI protocols, Fast Read and lastly
* (Slow) Read.
*/
-#define SNOR_HWCAPS_READ_MASK GENMASK(14, 0)
+#define SNOR_HWCAPS_READ_MASK GENMASK(15, 0)
#define SNOR_HWCAPS_READ BIT(0)
#define SNOR_HWCAPS_READ_FAST BIT(1)
#define SNOR_HWCAPS_READ_1_1_1_DTR BIT(2)
@@ -362,11 +363,12 @@ struct spi_nor_hwcaps {
#define SNOR_HWCAPS_READ_4_4_4 BIT(9)
#define SNOR_HWCAPS_READ_1_4_4_DTR BIT(10)

-#define SNOR_HWCAPS_READ_OCTAL GENMASK(14, 11)
+#define SNOR_HWCAPS_READ_OCTAL GENMASK(15, 11)
#define SNOR_HWCAPS_READ_1_1_8 BIT(11)
#define SNOR_HWCAPS_READ_1_8_8 BIT(12)
#define SNOR_HWCAPS_READ_8_8_8 BIT(13)
#define SNOR_HWCAPS_READ_1_8_8_DTR BIT(14)
+#define SNOR_HWCAPS_READ_8_8_8_DTR BIT(15)

/*
* Page Program capabilities.
@@ -377,18 +379,19 @@ struct spi_nor_hwcaps {
* JEDEC/SFDP standard to define them. Also at this moment no SPI flash memory
* implements such commands.
*/
-#define SNOR_HWCAPS_PP_MASK GENMASK(22, 16)
-#define SNOR_HWCAPS_PP BIT(16)
+#define SNOR_HWCAPS_PP_MASK GENMASK(23, 16)
+#define SNOR_HWCAPS_PP BIT(16)

-#define SNOR_HWCAPS_PP_QUAD GENMASK(19, 17)
-#define SNOR_HWCAPS_PP_1_1_4 BIT(17)
-#define SNOR_HWCAPS_PP_1_4_4 BIT(18)
-#define SNOR_HWCAPS_PP_4_4_4 BIT(19)
+#define SNOR_HWCAPS_PP_QUAD GENMASK(19, 17)
+#define SNOR_HWCAPS_PP_1_1_4 BIT(17)
+#define SNOR_HWCAPS_PP_1_4_4 BIT(18)
+#define SNOR_HWCAPS_PP_4_4_4 BIT(19)

-#define SNOR_HWCAPS_PP_OCTAL GENMASK(22, 20)
-#define SNOR_HWCAPS_PP_1_1_8 BIT(20)
-#define SNOR_HWCAPS_PP_1_8_8 BIT(21)
-#define SNOR_HWCAPS_PP_8_8_8 BIT(22)
+#define SNOR_HWCAPS_PP_OCTAL GENMASK(23, 20)
+#define SNOR_HWCAPS_PP_1_1_8 BIT(20)
+#define SNOR_HWCAPS_PP_1_8_8 BIT(21)
+#define SNOR_HWCAPS_PP_8_8_8 BIT(22)
+#define SNOR_HWCAPS_PP_8_8_8_DTR BIT(23)

#define SNOR_HWCAPS_X_X_X (SNOR_HWCAPS_READ_2_2_2 | \
SNOR_HWCAPS_READ_4_4_4 | \
@@ -399,7 +402,8 @@ struct spi_nor_hwcaps {
#define SNOR_HWCAPS_DTR (SNOR_HWCAPS_READ_1_1_1_DTR | \
SNOR_HWCAPS_READ_1_2_2_DTR | \
SNOR_HWCAPS_READ_1_4_4_DTR | \
- SNOR_HWCAPS_READ_1_8_8_DTR)
+ SNOR_HWCAPS_READ_1_8_8_DTR | \
+ SNOR_HWCAPS_READ_8_8_8_DTR)

#define SNOR_HWCAPS_ALL (SNOR_HWCAPS_READ_MASK | \
SNOR_HWCAPS_PP_MASK)
@@ -438,6 +442,7 @@ enum spi_nor_read_command_index {
SNOR_CMD_READ_1_8_8,
SNOR_CMD_READ_8_8_8,
SNOR_CMD_READ_1_8_8_DTR,
+ SNOR_CMD_READ_8_8_8_DTR,

SNOR_CMD_READ_MAX
};
@@ -454,6 +459,7 @@ enum spi_nor_pp_command_index {
SNOR_CMD_PP_1_1_8,
SNOR_CMD_PP_1_8_8,
SNOR_CMD_PP_8_8_8,
+ SNOR_CMD_PP_8_8_8_DTR,

SNOR_CMD_PP_MAX
};
@@ -570,6 +576,7 @@ struct flash_info;
* @program_opcode: the program opcode
* @sst_write_second: used by the SST write operation
* @flags: flag options for the current SPI-NOR (SNOR_F_*)
+ * @cmd_ext: the command opcode extension for DTR mode.
* @read_proto: the SPI protocol for read operations
* @write_proto: the SPI protocol for write operations
* @reg_proto the SPI protocol for read_reg/write_reg/erase operations
@@ -599,6 +606,7 @@ struct spi_nor {
enum spi_nor_protocol reg_proto;
bool sst_write_second;
u32 flags;
+ enum spi_mem_cmd_ext cmd_ext;

const struct spi_nor_controller_ops *controller_ops;

--
2.25.0

2020-02-26 09:38:29

by Pratyush Yadav

[permalink] [raw]
Subject: [PATCH v2 08/11] mtd: spi-nor: parse xSPI Profile 1.0 table

This table is indication that the flash is xSPI compliant and hence
supports octal DTR mode. Extract information like the fast read opcode,
the number of dummy cycles needed for a Read Status Register command,
and the number of address bytes needed for a Read Status Register
command.

The default dummy cycles for a fast octal DTR read are set to 20. Since
there is no simple way of determining the dummy cycles needed for the
fast read command, flashes that use a different value should update it
in their flash-specific hooks.

Signed-off-by: Pratyush Yadav <[email protected]>
---
drivers/mtd/spi-nor/spi-nor.c | 80 +++++++++++++++++++++++++++++++++++
include/linux/mtd/spi-nor.h | 5 +++
2 files changed, 85 insertions(+)

diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index c86c1537f76e..22784c403d77 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -16,6 +16,7 @@
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/sort.h>
+#include <linux/bitfield.h>

#include <linux/mtd/mtd.h>
#include <linux/of_platform.h>
@@ -58,12 +59,14 @@ struct sfdp_parameter_header {
#define SFDP_BFPT_ID 0xff00 /* Basic Flash Parameter Table */
#define SFDP_SECTOR_MAP_ID 0xff81 /* Sector Map Table */
#define SFDP_4BAIT_ID 0xff84 /* 4-byte Address Instruction Table */
+#define SFDP_PROFILE1_ID 0xff05 /* xSPI Profile 1.0 table. */

#define SFDP_SIGNATURE 0x50444653U
#define SFDP_JESD216_MAJOR 1
#define SFDP_JESD216_MINOR 0
#define SFDP_JESD216A_MINOR 5
#define SFDP_JESD216B_MINOR 6
+#define SFDP_JESD216D_MINOR 8

struct sfdp_header {
u32 signature; /* Ox50444653U <=> "SFDP" */
@@ -158,6 +161,11 @@ struct sfdp_bfpt {
u32 dwords[BFPT_DWORD_MAX];
};

+/* xSPI Profile 1.0 table (from JESD216D.01). */
+#define PROFILE1_DWORD1_RD_FAST_CMD GENMASK(15, 8)
+#define PROFILE1_DWORD1_RDSR_DUMMY BIT(28)
+#define PROFILE1_DWORD1_RDSR_ADDR_BYTES BIT(29)
+
/**
* struct spi_nor_fixups - SPI NOR fixup hooks
* @default_init: called after default flash parameters init. Used to tweak
@@ -4426,6 +4434,74 @@ static int spi_nor_parse_4bait(struct spi_nor *nor,
return ret;
}

+/**
+ * spi_nor_parse_profile1() - parse the xSPI Profile 1.0 table
+ * @nor: pointer to a 'struct spi_nor'
+ * @param_header: pointer to the 'struct sfdp_parameter_header' describing
+ * the 4-Byte Address Instruction Table length and version.
+ * @params: pointer to the 'struct spi_nor_flash_parameter' to be.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int spi_nor_parse_profile1(struct spi_nor *nor,
+ const struct sfdp_parameter_header *profile1_header,
+ struct spi_nor_flash_parameter *params)
+{
+ u32 *table, opcode, addr;
+ size_t len;
+ int ret, i;
+
+ len = profile1_header->length * sizeof(*table);
+ table = kmalloc(len, GFP_KERNEL);
+ if (!table)
+ return -ENOMEM;
+
+ addr = SFDP_PARAM_HEADER_PTP(profile1_header);
+ ret = spi_nor_read_sfdp(nor, addr, len, table);
+ if (ret)
+ goto out;
+
+ /* Fix endianness of the table DWORDs. */
+ for (i = 0; i < profile1_header->length; i++)
+ table[i] = le32_to_cpu(table[i]);
+
+ /* Get 8D-8D-8D fast read opcode and dummy cycles. */
+ opcode = FIELD_GET(PROFILE1_DWORD1_RD_FAST_CMD, table[0]);
+
+ /*
+ * Update the fast read settings. We set the default dummy cycles to 20
+ * here. Flashes can change this value if they need to when enabling
+ * octal mode.
+ */
+ params->hwcaps.mask |= SNOR_HWCAPS_READ_8_8_8_DTR;
+ spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_8_8_8_DTR],
+ 0, 20, opcode,
+ SNOR_PROTO_8_8_8_DTR);
+
+ /*
+ * Since the flash supports xSPI DTR reads, it should also support DTR
+ * Page Program opcodes.
+ */
+ params->hwcaps.mask |= SNOR_HWCAPS_PP_8_8_8_DTR;
+
+ /*
+ * Set the Read Status Register dummy cycles and dummy address bytes.
+ */
+ if (table[0] & PROFILE1_DWORD1_RDSR_DUMMY)
+ params->rdsr_dummy = 8;
+ else
+ params->rdsr_dummy = 4;
+
+ if (table[0] & PROFILE1_DWORD1_RDSR_ADDR_BYTES)
+ params->rdsr_addr_nbytes = 4;
+ else
+ params->rdsr_addr_nbytes = 0;
+
+out:
+ kfree(table);
+ return ret;
+}
+
/**
* spi_nor_parse_sfdp() - parse the Serial Flash Discoverable Parameters.
* @nor: pointer to a 'struct spi_nor'
@@ -4527,6 +4603,10 @@ static int spi_nor_parse_sfdp(struct spi_nor *nor,
err = spi_nor_parse_4bait(nor, param_header, params);
break;

+ case SFDP_PROFILE1_ID:
+ err = spi_nor_parse_profile1(nor, param_header, params);
+ break;
+
default:
break;
}
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 364f37276d78..f54dbd0f86ab 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -515,6 +515,9 @@ struct spi_nor_locking_ops {
*
* @size: the flash memory density in bytes.
* @page_size: the page size of the SPI NOR flash memory.
+ * @rdsr_dummy: dummy cycles needed for Read Status Register command.
+ * @rdsr_addr_nbytes: dummy address bytes needed for Read Status Register
+ * command.
* @hwcaps: describes the read and page program hardware
* capabilities.
* @reads: read capabilities ordered by priority: the higher index
@@ -537,6 +540,8 @@ struct spi_nor_locking_ops {
struct spi_nor_flash_parameter {
u64 size;
u32 page_size;
+ u8 rdsr_dummy;
+ u8 rdsr_addr_nbytes;

struct spi_nor_hwcaps hwcaps;
struct spi_nor_read_command reads[SNOR_CMD_READ_MAX];
--
2.25.0

2020-02-26 09:38:32

by Pratyush Yadav

[permalink] [raw]
Subject: [PATCH v2 09/11] mtd: spi-nor: use dummy cycle and address width info from SFDP

The xSPI Profile 1.0 table specifies how many dummy cycles and address
bytes are needed for the Read Status Register command in octal DTR mode.
Use that information to send the correct Read SR command.

Signed-off-by: Pratyush Yadav <[email protected]>
---
drivers/mtd/spi-nor/spi-nor.c | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index 22784c403d77..06cfc525260c 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -525,6 +525,8 @@ static int spi_nor_write_disable(struct spi_nor *nor)
static int spi_nor_read_sr(struct spi_nor *nor, u8 *sr)
{
int ret;
+ u8 addr_bytes = nor->params.rdsr_addr_nbytes;
+ u8 dummy = nor->params.rdsr_dummy;

if (nor->spimem) {
struct spi_mem_op op =
@@ -533,10 +535,21 @@ static int spi_nor_read_sr(struct spi_nor *nor, u8 *sr)
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_IN(1, sr, 1));

+ if (spi_nor_protocol_is_dtr(nor->reg_proto)) {
+ op.addr.nbytes = addr_bytes;
+ op.addr.val = 0;
+ op.dummy.nbytes = dummy;
+ }
+
+ 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);
+ if (spi_nor_protocol_is_dtr(nor->reg_proto))
+ ret = -ENOTSUPP;
+ else
+ ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDSR,
+ sr, 1);
}

if (ret)
--
2.25.0

2020-02-26 09:38:52

by Pratyush Yadav

[permalink] [raw]
Subject: [PATCH v2 07/11] mtd: spi-nor: get command opcode extension type from BFPT

Some devices in DTR mode expect an extra command byte called the
extension. The extension can either be same as the opcode, bitwise
inverse of the opcode, or another additional byte forming a 16-byte
opcode. Get the extension type from the BFPT. For now, only flashes with
"repeat" and "inverse" extensions are supported.

As of JESD216D.01, BFPT is 20 DWORDs, so update the table size to
reflect that.

Signed-off-by: Pratyush Yadav <[email protected]>
---
drivers/mtd/spi-nor/spi-nor.c | 30 +++++++++++++++++++++++++++---
1 file changed, 27 insertions(+), 3 deletions(-)

diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index 6102520550a1..c86c1537f76e 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -79,11 +79,11 @@ struct sfdp_header {
/* Basic Flash Parameter Table */

/*
- * JESD216 rev B defines a Basic Flash Parameter Table of 16 DWORDs.
- * They are indexed from 1 but C arrays are indexed from 0.
+ * JESD216 rev D defines a Basic Flash Parameter Table of 20 DWORDs. They are
+ * indexed from 1 but C arrays are indexed from 0.
*/
#define BFPT_DWORD(i) ((i) - 1)
-#define BFPT_DWORD_MAX 16
+#define BFPT_DWORD_MAX 20

/* The first version of JESD216 defined only 9 DWORDs. */
#define BFPT_DWORD_MAX_JESD216 9
@@ -148,6 +148,12 @@ struct sfdp_header {
#define BFPT_DWORD15_QER_SR2_BIT1_NO_RD (0x4UL << 20)
#define BFPT_DWORD15_QER_SR2_BIT1 (0x5UL << 20) /* Spansion */

+#define BFPT_DWORD18_CMD_EXT_MASK GENMASK(30, 29)
+#define BFPT_DWORD18_CMD_EXT_REP (0x0UL << 29) /* Repeat */
+#define BFPT_DWORD18_CMD_EXT_INV (0x1UL << 29) /* Invert */
+#define BFPT_DWORD18_CMD_EXT_RES (0x2UL << 29) /* Reserved */
+#define BFPT_DWORD18_CMD_EXT_16B (0x3UL << 29) /* 16-bit opcode */
+
struct sfdp_bfpt {
u32 dwords[BFPT_DWORD_MAX];
};
@@ -3867,6 +3873,24 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
return -EINVAL;
}

+ /* 8D-8D-8D command extension. */
+ switch (bfpt.dwords[BFPT_DWORD(18)] & BFPT_DWORD18_CMD_EXT_MASK) {
+ case BFPT_DWORD18_CMD_EXT_REP:
+ nor->cmd_ext = SPI_MEM_EXT_REPEAT;
+ break;
+
+ case BFPT_DWORD18_CMD_EXT_INV:
+ nor->cmd_ext = SPI_MEM_EXT_INVERT;
+ break;
+
+ case BFPT_DWORD18_CMD_EXT_RES:
+ return -EINVAL;
+
+ case BFPT_DWORD18_CMD_EXT_16B:
+ dev_err(nor->dev, "16-bit opcodes not supported\n");
+ return -ENOTSUPP;
+ }
+
return spi_nor_post_bfpt_fixups(nor, bfpt_header, &bfpt, params);
}

--
2.25.0

2020-02-26 09:39:15

by Pratyush Yadav

[permalink] [raw]
Subject: [PATCH v2 01/11] dt-bindings: spi: allow expressing DTR capability

Allow spi devices to express DTR receive and transmit capabilities via
the properties "spi-rx-dtr" and "spi-tx-dtr".

Signed-off-by: Pratyush Yadav <[email protected]>
---
.../devicetree/bindings/spi/spi-controller.yaml | 10 ++++++++++
1 file changed, 10 insertions(+)

diff --git a/Documentation/devicetree/bindings/spi/spi-controller.yaml b/Documentation/devicetree/bindings/spi/spi-controller.yaml
index 1e0ca6ccf64b..7a84debed213 100644
--- a/Documentation/devicetree/bindings/spi/spi-controller.yaml
+++ b/Documentation/devicetree/bindings/spi/spi-controller.yaml
@@ -120,6 +120,11 @@ patternProperties:
description:
Delay, in microseconds, after a read transfer.

+ spi-rx-dtr:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Device supports receiving in DTR mode.
+
spi-tx-bus-width:
allOf:
- $ref: /schemas/types.yaml#/definitions/uint32
@@ -132,6 +137,11 @@ patternProperties:
description:
Delay, in microseconds, after a write transfer.

+ spi-tx-dtr:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Device supports transmitting in DTR mode.
+
required:
- compatible
- reg
--
2.25.0

2020-02-26 09:39:41

by Pratyush Yadav

[permalink] [raw]
Subject: [PATCH v2 10/11] mtd: spi-nor: enable octal DTR mode when possible

Allow flashes to specify a hook to enable octal DTR mode. Use this hook
whenever possible to get optimal transfer speeds.

Signed-off-by: Pratyush Yadav <[email protected]>
---
drivers/mtd/spi-nor/spi-nor.c | 31 +++++++++++++++++++++++++++++++
include/linux/mtd/spi-nor.h | 2 ++
2 files changed, 33 insertions(+)

diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index 06cfc525260c..fdcc1b4abc3f 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -5184,6 +5184,31 @@ static void spi_nor_init_params(struct spi_nor *nor)
spi_nor_late_init_params(nor);
}

+/** spi_nor_octal_dtr_enable() - enable Octal DTR I/O if needed
+ * @nor: pointer to a 'struct spi_nor'
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int spi_nor_octal_dtr_enable(struct spi_nor *nor)
+{
+ int ret;
+
+ if (!nor->params.octal_dtr_enable)
+ return 0;
+
+ if (!(spi_nor_get_protocol_width(nor->read_proto) == 8 ||
+ spi_nor_get_protocol_width(nor->write_proto) == 8))
+ return 0;
+
+ ret = nor->params.octal_dtr_enable(nor);
+ if (ret)
+ return ret;
+
+ nor->reg_proto = SNOR_PROTO_8_8_8_DTR;
+
+ return 0;
+}
+
/**
* spi_nor_quad_enable() - enable Quad I/O if needed.
* @nor: pointer to a 'struct spi_nor'
@@ -5223,6 +5248,12 @@ static int spi_nor_init(struct spi_nor *nor)
{
int err;

+ err = spi_nor_octal_dtr_enable(nor);
+ if (err) {
+ dev_dbg(nor->dev, "octal mode not supported\n");
+ return err;
+ }
+
err = spi_nor_quad_enable(nor);
if (err) {
dev_dbg(nor->dev, "quad mode not supported\n");
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index f54dbd0f86ab..c653f6713cfc 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -526,6 +526,7 @@ struct spi_nor_locking_ops {
* higher index in the array, the higher priority.
* @erase_map: the erase map parsed from the SFDP Sector Map Parameter
* Table.
+ * @octal_dtr_enable: enables SPI NOR octal DTR mode.
* @quad_enable: enables SPI NOR quad mode.
* @set_4byte: puts the SPI NOR in 4 byte addressing mode.
* @convert_addr: converts an absolute address into something the flash
@@ -549,6 +550,7 @@ struct spi_nor_flash_parameter {

struct spi_nor_erase_map erase_map;

+ int (*octal_dtr_enable)(struct spi_nor *nor);
int (*quad_enable)(struct spi_nor *nor);
int (*set_4byte)(struct spi_nor *nor, bool enable);
u32 (*convert_addr)(struct spi_nor *nor, u32 addr);
--
2.25.0

2020-02-26 09:39:44

by Pratyush Yadav

[permalink] [raw]
Subject: [PATCH v2 04/11] spi: spi-mem: allow specifying a command's extension

In xSPI mode, flashes expect 2-byte opcodes. The second byte is called
the "command extension". There can be 3 types of extensions in xSPI:
repeat, invert, and hex. When the extension type is "repeat", the same
opcode is sent twice. When it is "invert", the second byte is the
inverse of the opcode. When it is "hex" an additional opcode byte based
is sent with the command whose value can be anything.

Signed-off-by: Pratyush Yadav <[email protected]>
---
drivers/spi/spi-mem.c | 23 +++++++++++++++++++++++
include/linux/spi/spi-mem.h | 24 ++++++++++++++++++++++++
2 files changed, 47 insertions(+)

diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c
index cb13e0878b95..3838ddc9aeec 100644
--- a/drivers/spi/spi-mem.c
+++ b/drivers/spi/spi-mem.c
@@ -462,6 +462,29 @@ int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
}
EXPORT_SYMBOL_GPL(spi_mem_adjust_op_size);

+int spi_mem_get_cmd_ext(const struct spi_mem_op *op, u8 *ext)
+{
+ switch (op->cmd.ext_type) {
+ case SPI_MEM_EXT_INVERT:
+ *ext = ~op->cmd.opcode;
+ break;
+
+ case SPI_MEM_EXT_REPEAT:
+ *ext = op->cmd.opcode;
+ break;
+
+ case SPI_MEM_EXT_HEX:
+ *ext = op->cmd.ext;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(spi_mem_get_cmd_ext);
+
static ssize_t spi_mem_no_dirmap_read(struct spi_mem_dirmap_desc *desc,
u64 offs, size_t len, void *buf)
{
diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h
index 4669082b4e3b..06ccab17e4d0 100644
--- a/include/linux/spi/spi-mem.h
+++ b/include/linux/spi/spi-mem.h
@@ -67,11 +67,31 @@ enum spi_mem_data_dir {
SPI_MEM_DATA_OUT,
};

+/**
+ * enum spi_mem_cmd_ext - describes the command opcode extension in DTR mode
+ * @SPI_MEM_EXT_NONE: no extension. This is the default, and is used in Legacy
+ * SPI mode
+ * @SPI_MEM_EXT_REPEAT: the extension is same as the opcode
+ * @SPI_MEM_EXT_INVERT: the extension is the bitwise inverse of the opcode
+ * @SPI_MEM_EXT_HEX: the extension is any hex value. The command and opcode
+ * combine to form a 16-bit opcode.
+ */
+enum spi_mem_cmd_ext {
+ SPI_MEM_EXT_NONE = 0,
+ SPI_MEM_EXT_REPEAT,
+ SPI_MEM_EXT_INVERT,
+ SPI_MEM_EXT_HEX,
+};
+
/**
* struct spi_mem_op - describes a SPI memory operation
* @cmd.buswidth: number of IO lines used to transmit the command
* @cmd.opcode: operation opcode
* @cmd.is_dtr: whether the command opcode should be sent in DTR mode or not
+ * @cmd.ext_type: type of the command opcode extension in DTR mode
+ * @cmd.ext: value of the command opcode extension in DTR mode. It is
+ * only set when 'ext_type' is 'SPI_MEM_EXT_HEX'. In all other
+ * cases, the extension can be directly derived from the opcode.
* @addr.nbytes: number of address bytes to send. Can be zero if the operation
* does not need to send an address
* @addr.buswidth: number of IO lines used to transmit the address cycles
@@ -97,6 +117,8 @@ struct spi_mem_op {
u8 buswidth;
u8 opcode;
bool is_dtr;
+ enum spi_mem_cmd_ext ext_type;
+ u8 ext;
} cmd;

struct {
@@ -361,6 +383,8 @@ int spi_mem_driver_register_with_owner(struct spi_mem_driver *drv,

void spi_mem_driver_unregister(struct spi_mem_driver *drv);

+int spi_mem_get_cmd_ext(const struct spi_mem_op *op, u8 *ext);
+
#define spi_mem_driver_register(__drv) \
spi_mem_driver_register_with_owner(__drv, THIS_MODULE)

--
2.25.0

2020-02-26 09:39:49

by Pratyush Yadav

[permalink] [raw]
Subject: [PATCH v2 11/11] mtd: spi-nor: add support for Cypress Semper flash

The Cypress Semper flash is an xSPI compliant octal DTR flash. Add
support for using it in octal DTR mode.

The flash by default boots in a hybrid sector mode. Switch to uniform
sector mode on boot. Use the default 20 dummy cycles for a read fast
command.

The SFDP programming on some older versions of the flash was incorrect.
Fixes for that are included in the fixup hooks.

Signed-off-by: Pratyush Yadav <[email protected]>
---
drivers/mtd/spi-nor/spi-nor.c | 191 ++++++++++++++++++++++++++++++++++
include/linux/mtd/spi-nor.h | 14 +++
2 files changed, 205 insertions(+)

diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index fdcc1b4abc3f..a01acecaa568 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -260,6 +260,12 @@ struct flash_info {

#define JEDEC_MFR(info) ((info)->id[0])

+/* Forward declarations that will be used by s28hs512t_setup(). */
+static int spi_nor_default_setup(struct spi_nor *nor,
+ const struct spi_nor_hwcaps *hwcaps);
+static void spi_nor_set_pp_settings(struct spi_nor_pp_command *pp,
+ u8 opcode, enum spi_nor_protocol proto);
+
/**
* spi_nor_spimem_setup_op() - Set up common properties of a spi-mem op.
* @nor: pointer to a 'struct spi_nor'
@@ -2241,6 +2247,64 @@ static int spi_nor_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len)
return ret;
}

+/**
+ * spi_nor_cypress_octal_enable() - Enable octal DTR mode on Cypress flashes.
+ * @nor: pointer to a 'struct spi_nor'
+ *
+ * This also sets the memory access latency cycles to 20, which is the default
+ * in the spi-nor framework.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int spi_nor_cypress_octal_enable(struct spi_nor *nor)
+{
+ struct spi_mem_op op;
+ u8 *buf = nor->bouncebuf;
+ u8 addr_width = 3;
+ int ret;
+
+ /* Use 20 dummy cycles for memory array reads. */
+ ret = spi_nor_write_enable(nor);
+ if (ret)
+ return ret;
+
+ *buf = SPINOR_REG_CYPRESS_CFR2V_MEMLAT_8_20;
+ op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WR_ANY_REG, 1),
+ SPI_MEM_OP_ADDR(addr_width, SPINOR_REG_CYPRESS_CFR2V, 1),
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_OUT(1, buf, 1));
+ ret = spi_mem_exec_op(nor->spimem, &op);
+ if (ret) {
+ dev_warn(nor->dev,
+ "failed to set default memory latency value: %d\n",
+ ret);
+ return ret;
+ }
+ ret = spi_nor_wait_till_ready(nor);
+ if (ret)
+ return ret;
+
+ nor->read_dummy = 20;
+
+ /* Set the octal and DTR enable bits. */
+ ret = spi_nor_write_enable(nor);
+ if (ret)
+ return ret;
+
+ *buf = SPINOR_REG_CYPRESS_CFR5V_OCT_DTR_EN;
+ op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WR_ANY_REG, 1),
+ SPI_MEM_OP_ADDR(addr_width, SPINOR_REG_CYPRESS_CFR5V, 1),
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_OUT(1, buf, 1));
+ ret = spi_mem_exec_op(nor->spimem, &op);
+ if (ret) {
+ dev_warn(nor->dev, "Failed to enable octal DTR mode\n");
+ return ret;
+ }
+
+ return 0;
+}
+
/**
* spi_nor_sr1_bit6_quad_enable() - Set the Quad Enable BIT(6) in the Status
* Register 1.
@@ -2453,6 +2517,130 @@ static struct spi_nor_fixups gd25q256_fixups = {
.default_init = gd25q256_default_init,
};

+static int s28hs512t_setup(struct spi_nor *nor,
+ const struct spi_nor_hwcaps *hwcaps)
+{
+ struct spi_mem_op op;
+ u8 *buf = nor->bouncebuf;
+ u8 addr_width = 3;
+ int ret;
+
+ if (!nor->spimem) {
+ dev_err(nor->dev,
+ "operation not supported for non-spimem drivers\n");
+ return -ENOTSUPP;
+ }
+
+ /*
+ * This Cypress flash also supports hybrid sector sizes. Make sure
+ * uniform sector mode is selected. This is done by setting the bit
+ * CFR3N[3].
+ */
+ op = (struct spi_mem_op)
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RD_ANY_REG, 1),
+ SPI_MEM_OP_ADDR(addr_width, SPINOR_REG_CYPRESS_CFR3N, 1),
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_IN(1, buf, 1));
+ ret = spi_mem_exec_op(nor->spimem, &op);
+ if (ret)
+ return ret;
+
+ ret = spi_nor_write_enable(nor);
+ if (ret)
+ return ret;
+
+ /* Set the uniform sector mode bit. */
+ *buf |= SPINOR_REG_CYPRESS_CFR3N_UNISECT;
+ op = (struct spi_mem_op)
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WR_ANY_REG, 1),
+ SPI_MEM_OP_ADDR(addr_width, SPINOR_REG_CYPRESS_CFR3N, 1),
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_OUT(1, buf, 1));
+ ret = spi_mem_exec_op(nor->spimem, &op);
+ if (ret) {
+ dev_err(nor->dev, "Failed to change to uniform sector mode\n");
+ return ret;
+ }
+
+ ret = spi_nor_wait_till_ready(nor);
+ if (ret)
+ return ret;
+
+ return spi_nor_default_setup(nor, hwcaps);
+}
+
+static void s28hs512t_default_init(struct spi_nor *nor)
+{
+ nor->params.octal_dtr_enable = spi_nor_cypress_octal_enable;
+ nor->params.setup = s28hs512t_setup;
+}
+
+static void s28hs512t_post_sfdp_fixup(struct spi_nor *nor)
+{
+ /*
+ * On older versions of the flash the xSPI Profile 1.0 table has the
+ * 8D-8D-8D Fast Read opcode as 0x00. But it actually should be 0xEE.
+ */
+ if (nor->params.reads[SNOR_CMD_READ_8_8_8_DTR].opcode == 0)
+ nor->params.reads[SNOR_CMD_READ_8_8_8_DTR].opcode =
+ SPINOR_OP_CYPRESS_RD_FAST;
+
+ /* This flash is also missing the 4-byte Page Program opcode bit. */
+ spi_nor_set_pp_settings(&nor->params.page_programs[SNOR_CMD_PP],
+ SPINOR_OP_PP_4B, SNOR_PROTO_1_1_1);
+ /*
+ * Since xSPI Page Program opcode is backward compatible with
+ * Legacy SPI, use Legacy SPI opcode there as well.
+ */
+ spi_nor_set_pp_settings(&nor->params.page_programs[SNOR_CMD_PP_8_8_8_DTR],
+ SPINOR_OP_PP_4B, SNOR_PROTO_8_8_8_DTR);
+
+ /*
+ * The xSPI Profile 1.0 table advertises the number of additional
+ * address bytes needed for Read Status Register command as 0 but the
+ * actual value for that is 4.
+ */
+ nor->params.rdsr_addr_nbytes = 4;
+}
+
+static int s28hs512t_post_bfpt_fixup(struct spi_nor *nor,
+ const struct sfdp_parameter_header *bfpt_header,
+ const struct sfdp_bfpt *bfpt,
+ struct spi_nor_flash_parameter *params)
+{
+ struct spi_mem_op op;
+ u8 *buf = nor->bouncebuf;
+ u8 addr_width = 3;
+ int ret;
+
+ /*
+ * The BFPT table advertises a 512B page size but the page size is
+ * actually configurable (with the default being 256B). Read from
+ * CFR3V[4] and set the correct size.
+ */
+ op = (struct spi_mem_op)
+ SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RD_ANY_REG, 1),
+ SPI_MEM_OP_ADDR(addr_width, SPINOR_REG_CYPRESS_CFR3V, 1),
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_IN(1, buf, 1));
+ ret = spi_mem_exec_op(nor->spimem, &op);
+ if (ret)
+ return ret;
+
+ if (*buf & SPINOR_REG_CYPRESS_CFR3V_PGSZ)
+ params->page_size = 512;
+ else
+ params->page_size = 256;
+
+ return 0;
+}
+
+static struct spi_nor_fixups s28hs512t_fixups = {
+ .default_init = s28hs512t_default_init,
+ .post_sfdp = s28hs512t_post_sfdp_fixup,
+ .post_bfpt = s28hs512t_post_bfpt_fixup,
+};
+
/* NOTE: double check command sets and memory organization when you add
* more nor chips. This current list focusses on newer chips, which
* have been converging on command sets which including JEDEC ID.
@@ -2715,6 +2903,9 @@ static const struct flash_info spi_nor_ids[] = {
{ "s25fl064l", INFO(0x016017, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) },
{ "s25fl128l", INFO(0x016018, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) },
{ "s25fl256l", INFO(0x016019, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) },
+ { "s28hs512t", INFO(0x345b1a, 0, 256 * 1024, 256, SECT_4K | SPI_NOR_OCTAL_DTR_READ)
+ .fixups = &s28hs512t_fixups,
+ },

/* SST -- large erase sizes are "overlays", "sectors" are 4K */
{ "sst25vf040b", INFO(0xbf258d, 0, 64 * 1024, 8, SECT_4K | SST_WRITE) },
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index c653f6713cfc..e765272fc0f4 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -150,6 +150,20 @@
#define SR2_QUAD_EN_BIT1 BIT(1)
#define SR2_QUAD_EN_BIT7 BIT(7)

+/* For Cypress flash. */
+#define SPINOR_OP_RD_ANY_REG 0x65 /* Read any register */
+#define SPINOR_OP_WR_ANY_REG 0x71 /* Write any register */
+#define SPINOR_REG_CYPRESS_CFR2V 0x00800003
+#define SPINOR_REG_CYPRESS_CFR2V_MEMLAT_8_20 0x8
+#define SPINOR_REG_CYPRESS_CFR3N 0x00000004
+#define SPINOR_REG_CYPRESS_CFR3V 0x00800004
+#define SPINOR_REG_CYPRESS_CFR3V_PGSZ BIT(4) /* Page size. */
+#define SPINOR_REG_CYPRESS_CFR3N_UNISECT BIT(3) /* Uniform sector mode */
+#define SPINOR_REG_CYPRESS_CFR4V 0x00800005
+#define SPINOR_REG_CYPRESS_CFR5V 0x00800006
+#define SPINOR_REG_CYPRESS_CFR5V_OCT_DTR_EN 0x3
+#define SPINOR_OP_CYPRESS_RD_FAST 0xee
+
/* Supported SPI protocols */
#define SNOR_PROTO_INST_MASK GENMASK(23, 16)
#define SNOR_PROTO_INST_SHIFT 16
--
2.25.0

2020-02-26 09:40:00

by Pratyush Yadav

[permalink] [raw]
Subject: [PATCH v2 05/11] spi: cadence-quadspi: Add support for octal DTR flashes

Set up opcode extension and enable/disable DTR mode based on whether the
command is DTR or not.

xSPI flashes can have a 4-byte dummy address associated with some
commands like the Read Status Register command in octal DTR mode. Since
the flash does not support sending the dummy address, we can not use
automatic write completion polling in DTR mode. Further, no write
completion polling makes it impossible to use DAC mode for DTR writes.
In that mode, the controller does not know beforehand how long a write
will be and so it can de-assert Chip Select (CS#) at any time. Once CS#
is de-assert, the flash will go into burning phase. But since the
controller does not do write completion polling, it does not know when
the flash is busy and might send in writes while the flash is not ready.

So, disable write completion polling and make writes go through indirect
mode for DTR writes and let spi-mem take care of polling the SR.

Signed-off-by: Pratyush Yadav <[email protected]>
---
drivers/spi/spi-cadence-quadspi.c | 247 ++++++++++++++++++++++++++----
1 file changed, 214 insertions(+), 33 deletions(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index 000f01c18c87..d43bf30e1cde 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -52,6 +52,7 @@ struct cqspi_flash_pdata {
u8 inst_width;
u8 addr_width;
u8 data_width;
+ bool is_dtr;
u8 cs;
bool registered;
};
@@ -111,6 +112,8 @@ struct cqspi_driver_platdata {
#define CQSPI_REG_CONFIG_CHIPSELECT_LSB 10
#define CQSPI_REG_CONFIG_DMA_MASK BIT(15)
#define CQSPI_REG_CONFIG_BAUD_LSB 19
+#define CQSPI_REG_CONFIG_DTR_PROTO BIT(24)
+#define CQSPI_REG_CONFIG_DUAL_OPCODE BIT(30)
#define CQSPI_REG_CONFIG_IDLE_LSB 31
#define CQSPI_REG_CONFIG_CHIPSELECT_MASK 0xF
#define CQSPI_REG_CONFIG_BAUD_MASK 0xF
@@ -217,6 +220,14 @@ struct cqspi_driver_platdata {
#define CQSPI_REG_CMDWRITEDATALOWER 0xA8
#define CQSPI_REG_CMDWRITEDATAUPPER 0xAC

+#define CQSPI_REG_POLLING_STATUS 0xB0
+#define CQSPI_REG_POLLING_STATUS_DUMMY_LSB 16
+
+#define CQSPI_REG_OP_EXT_LOWER 0xE0
+#define CQSPI_REG_OP_EXT_READ_LSB 24
+#define CQSPI_REG_OP_EXT_WRITE_LSB 16
+#define CQSPI_REG_OP_EXT_STIG_LSB 0
+
/* Interrupt status bits */
#define CQSPI_REG_IRQ_MODE_ERR BIT(0)
#define CQSPI_REG_IRQ_UNDERFLOW BIT(1)
@@ -291,6 +302,69 @@ static unsigned int cqspi_calc_rdreg(struct cqspi_flash_pdata *f_pdata)
return rdreg;
}

+static int cqspi_set_protocol(struct cqspi_flash_pdata *f_pdata,
+ const struct spi_mem_op *op)
+{
+ f_pdata->inst_width = CQSPI_INST_TYPE_SINGLE;
+ f_pdata->addr_width = CQSPI_INST_TYPE_SINGLE;
+ f_pdata->data_width = CQSPI_INST_TYPE_SINGLE;
+ f_pdata->is_dtr = op->data.is_dtr && op->cmd.is_dtr && op->addr.is_dtr;
+
+ switch (op->data.buswidth) {
+ case 0:
+ break;
+ case 1:
+ f_pdata->data_width = CQSPI_INST_TYPE_SINGLE;
+ break;
+ case 2:
+ f_pdata->data_width = CQSPI_INST_TYPE_DUAL;
+ break;
+ case 4:
+ f_pdata->data_width = CQSPI_INST_TYPE_QUAD;
+ break;
+ case 8:
+ f_pdata->data_width = CQSPI_INST_TYPE_OCTAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Right now we only support 8-8-8 DTR mode. */
+ if (f_pdata->is_dtr) {
+ switch (op->cmd.buswidth) {
+ case 0:
+ break;
+ case 8:
+ f_pdata->inst_width = CQSPI_INST_TYPE_OCTAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (op->addr.buswidth) {
+ case 0:
+ break;
+ case 8:
+ f_pdata->addr_width = CQSPI_INST_TYPE_OCTAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (op->data.buswidth) {
+ case 0:
+ break;
+ case 8:
+ f_pdata->data_width = CQSPI_INST_TYPE_OCTAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
static int cqspi_wait_idle(struct cqspi_st *cqspi)
{
const unsigned int poll_idle_retry = 3;
@@ -348,6 +422,60 @@ static int cqspi_exec_flash_cmd(struct cqspi_st *cqspi, unsigned int reg)
return cqspi_wait_idle(cqspi);
}

+static int cqspi_setup_opcode_ext(struct cqspi_flash_pdata *f_pdata,
+ const struct spi_mem_op *op,
+ unsigned int shift)
+{
+ struct cqspi_st *cqspi = f_pdata->cqspi;
+ void __iomem *reg_base = cqspi->iobase;
+ int ret;
+ unsigned int reg;
+ u8 ext;
+
+ ret = spi_mem_get_cmd_ext(op, &ext);
+ if (ret)
+ return ret;
+ reg = readl(reg_base + CQSPI_REG_OP_EXT_LOWER);
+ reg &= ~(0xff << shift);
+ reg |= ext << shift;
+ writel(reg, reg_base + CQSPI_REG_OP_EXT_LOWER);
+
+ return 0;
+}
+
+static int cqspi_enable_dtr(struct cqspi_flash_pdata *f_pdata,
+ const struct spi_mem_op *op, unsigned int shift,
+ bool enable)
+{
+ struct cqspi_st *cqspi = f_pdata->cqspi;
+ void __iomem *reg_base = cqspi->iobase;
+ unsigned int reg;
+ int ret;
+
+ reg = readl(reg_base + CQSPI_REG_CONFIG);
+
+ /*
+ * We enable dual byte opcode here. The callers have to set up the
+ * extension opcode based on which type of operation it is.
+ */
+ if (enable) {
+ reg |= CQSPI_REG_CONFIG_DTR_PROTO;
+ reg |= CQSPI_REG_CONFIG_DUAL_OPCODE;
+
+ /* Set up command opcode extension. */
+ ret = cqspi_setup_opcode_ext(f_pdata, op, shift);
+ if (ret)
+ return ret;
+ } else {
+ reg &= ~CQSPI_REG_CONFIG_DTR_PROTO;
+ reg &= ~CQSPI_REG_CONFIG_DUAL_OPCODE;
+ }
+
+ writel(reg, reg_base + CQSPI_REG_CONFIG);
+
+ return cqspi_wait_idle(cqspi);
+}
+
static int cqspi_command_read(struct cqspi_flash_pdata *f_pdata,
const struct spi_mem_op *op)
{
@@ -361,6 +489,15 @@ static int cqspi_command_read(struct cqspi_flash_pdata *f_pdata,
size_t read_len;
int status;

+ status = cqspi_set_protocol(f_pdata, op);
+ if (status)
+ return status;
+
+ status = cqspi_enable_dtr(f_pdata, op, CQSPI_REG_OP_EXT_STIG_LSB,
+ f_pdata->is_dtr);
+ if (status)
+ return status;
+
if (!n_rx || n_rx > CQSPI_STIG_DATA_LEN_MAX || !rxbuf) {
dev_err(&cqspi->pdev->dev,
"Invalid input argument, len %zu rxbuf 0x%p\n",
@@ -410,6 +547,16 @@ static int cqspi_command_write(struct cqspi_flash_pdata *f_pdata,
unsigned int reg;
unsigned int data;
size_t write_len;
+ int ret;
+
+ ret = cqspi_set_protocol(f_pdata, op);
+ if (ret)
+ return ret;
+
+ ret = cqspi_enable_dtr(f_pdata, op, CQSPI_REG_OP_EXT_STIG_LSB,
+ f_pdata->is_dtr);
+ if (ret)
+ return ret;

if (n_tx > CQSPI_STIG_DATA_LEN_MAX || (n_tx && !txbuf)) {
dev_err(&cqspi->pdev->dev,
@@ -418,6 +565,9 @@ static int cqspi_command_write(struct cqspi_flash_pdata *f_pdata,
return -EINVAL;
}

+ reg = cqspi_calc_rdreg(f_pdata);
+ writel(reg, reg_base + CQSPI_REG_RD_INSTR);
+
reg = opcode << CQSPI_REG_CMDCTRL_OPCODE_LSB;

if (op->addr.nbytes) {
@@ -456,16 +606,25 @@ static int cqspi_read_setup(struct cqspi_flash_pdata *f_pdata,
void __iomem *reg_base = cqspi->iobase;
unsigned int dummy_clk = 0;
unsigned int reg;
+ int ret;
+
+ ret = cqspi_enable_dtr(f_pdata, op, CQSPI_REG_OP_EXT_READ_LSB,
+ f_pdata->is_dtr);
+ if (ret)
+ return ret;

reg = op->cmd.opcode << CQSPI_REG_RD_INSTR_OPCODE_LSB;
reg |= cqspi_calc_rdreg(f_pdata);

/* Setup dummy clock cycles */
- dummy_clk = op->dummy.nbytes * 8;
+ dummy_clk = op->dummy.nbytes * (8 / op->dummy.buswidth);
+ if (f_pdata->is_dtr)
+ dummy_clk /= 2;
+
if (dummy_clk > CQSPI_DUMMY_CLKS_MAX)
dummy_clk = CQSPI_DUMMY_CLKS_MAX;

- if (dummy_clk / 8)
+ if (dummy_clk)
reg |= (dummy_clk & CQSPI_REG_RD_INSTR_DUMMY_MASK)
<< CQSPI_REG_RD_INSTR_DUMMY_LSB;

@@ -575,15 +734,38 @@ static int cqspi_write_setup(struct cqspi_flash_pdata *f_pdata,
const struct spi_mem_op *op)
{
unsigned int reg;
+ int ret;
struct cqspi_st *cqspi = f_pdata->cqspi;
void __iomem *reg_base = cqspi->iobase;

+ ret = cqspi_enable_dtr(f_pdata, op, CQSPI_REG_OP_EXT_WRITE_LSB,
+ f_pdata->is_dtr);
+ if (ret)
+ return ret;
+
/* Set opcode. */
reg = op->cmd.opcode << CQSPI_REG_WR_INSTR_OPCODE_LSB;
+ reg |= f_pdata->data_width << CQSPI_REG_WR_INSTR_TYPE_DATA_LSB;
+ reg |= f_pdata->addr_width << CQSPI_REG_WR_INSTR_TYPE_ADDR_LSB;
writel(reg, reg_base + CQSPI_REG_WR_INSTR);
reg = cqspi_calc_rdreg(f_pdata);
writel(reg, reg_base + CQSPI_REG_RD_INSTR);

+ /* Set up the command opcode extension. */
+ if (f_pdata->is_dtr) {
+ /*
+ * Some flashes like the cypress Semper flash expect a 4-byte
+ * dummy address with the Read SR command in DTR mode, but this
+ * controller does not support sending address with the Read SR
+ * command. So, disable write completion polling on the
+ * controller's side. spi-nor will take care of polling the
+ * status register.
+ */
+ reg = readl(reg_base + CQSPI_REG_WR_COMPLETION_CTRL);
+ reg |= CQSPI_REG_WR_DISABLE_AUTO_POLL;
+ writel(reg, reg_base + CQSPI_REG_WR_COMPLETION_CTRL);
+ }
+
reg = readl(reg_base + CQSPI_REG_SIZE);
reg &= ~CQSPI_REG_SIZE_ADDRESS_MASK;
reg |= (op->addr.nbytes - 1);
@@ -833,35 +1015,6 @@ static void cqspi_configure(struct cqspi_flash_pdata *f_pdata,
cqspi_controller_enable(cqspi, 1);
}

-static int cqspi_set_protocol(struct cqspi_flash_pdata *f_pdata,
- const struct spi_mem_op *op)
-{
- f_pdata->inst_width = CQSPI_INST_TYPE_SINGLE;
- f_pdata->addr_width = CQSPI_INST_TYPE_SINGLE;
- f_pdata->data_width = CQSPI_INST_TYPE_SINGLE;
-
- if (op->data.dir == SPI_MEM_DATA_IN) {
- switch (op->data.buswidth) {
- case 1:
- f_pdata->data_width = CQSPI_INST_TYPE_SINGLE;
- break;
- case 2:
- f_pdata->data_width = CQSPI_INST_TYPE_DUAL;
- break;
- case 4:
- f_pdata->data_width = CQSPI_INST_TYPE_QUAD;
- break;
- case 8:
- f_pdata->data_width = CQSPI_INST_TYPE_OCTAL;
- break;
- default:
- return -EINVAL;
- }
- }
-
- return 0;
-}
-
static ssize_t cqspi_write(struct cqspi_flash_pdata *f_pdata,
const struct spi_mem_op *op)
{
@@ -879,7 +1032,16 @@ static ssize_t cqspi_write(struct cqspi_flash_pdata *f_pdata,
if (ret)
return ret;

- if (cqspi->use_dac_mode && ((to + len) <= cqspi->ahb_size)) {
+ /*
+ * Some flashes like the Cypress Semper flash expect a dummy 4-byte
+ * address (all 0s) with the read status register command in DTR mode.
+ * But this controller does not support sending dummy address bytes to
+ * the flash when it is polling the write completion register in DTR
+ * mode. So, we can not use direct mode when in DTR mode for writing
+ * data.
+ */
+ if (!f_pdata->is_dtr && cqspi->use_dac_mode
+ && ((to + len) <= cqspi->ahb_size)) {
memcpy_toio(cqspi->ahb_base + to, buf, len);
return cqspi_wait_idle(cqspi);
}
@@ -1006,6 +1168,23 @@ static int cqspi_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op *op)
return ret;
}

+static bool cqspi_supports_mem_op(struct spi_mem *mem,
+ const struct spi_mem_op *op)
+{
+ bool all_true, all_false;
+
+ all_true = op->cmd.is_dtr && op->addr.is_dtr && op->dummy.is_dtr &&
+ op->data.is_dtr;
+ all_false = !op->cmd.is_dtr && !op->addr.is_dtr && !op->dummy.is_dtr &&
+ !op->data.is_dtr;
+
+ /* Mixed DTR modes not supported. */
+ if (!(all_true || all_false))
+ return false;
+
+ return spi_mem_default_supports_op(mem, op);
+}
+
static int cqspi_of_get_flash_pdata(struct platform_device *pdev,
struct cqspi_flash_pdata *f_pdata,
struct device_node *np)
@@ -1136,6 +1315,7 @@ static int cqspi_request_mmap_dma(struct cqspi_st *cqspi)

static const struct spi_controller_mem_ops cqspi_mem_ops = {
.exec_op = cqspi_exec_mem_op,
+ .supports_op = cqspi_supports_mem_op,
};

static int cqspi_setup_flash(struct cqspi_st *cqspi)
@@ -1187,7 +1367,8 @@ static int cqspi_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "spi_alloc_master failed\n");
return -ENOMEM;
}
- master->mode_bits = SPI_RX_QUAD | SPI_TX_DUAL | SPI_RX_DUAL;
+ master->mode_bits = SPI_RX_OCTAL | SPI_TX_OCTAL | SPI_RX_QUAD |
+ SPI_TX_DUAL | SPI_RX_DUAL | SPI_RX_DTR | SPI_TX_DTR;
master->mem_ops = &cqspi_mem_ops;
master->dev.of_node = pdev->dev.of_node;

--
2.25.0

2020-02-27 16:12:32

by Boris Brezillon

[permalink] [raw]
Subject: Re: [PATCH v2 01/11] dt-bindings: spi: allow expressing DTR capability

On Wed, 26 Feb 2020 15:06:53 +0530
Pratyush Yadav <[email protected]> wrote:

> Allow spi devices to express DTR receive and transmit capabilities via
> the properties "spi-rx-dtr" and "spi-tx-dtr".

Is the RX/TX granularity really useful?

>
> Signed-off-by: Pratyush Yadav <[email protected]>
> ---
> .../devicetree/bindings/spi/spi-controller.yaml | 10 ++++++++++
> 1 file changed, 10 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/spi/spi-controller.yaml b/Documentation/devicetree/bindings/spi/spi-controller.yaml
> index 1e0ca6ccf64b..7a84debed213 100644
> --- a/Documentation/devicetree/bindings/spi/spi-controller.yaml
> +++ b/Documentation/devicetree/bindings/spi/spi-controller.yaml
> @@ -120,6 +120,11 @@ patternProperties:
> description:
> Delay, in microseconds, after a read transfer.
>
> + spi-rx-dtr:
> + $ref: /schemas/types.yaml#/definitions/flag
> + description:
> + Device supports receiving in DTR mode.
> +
> spi-tx-bus-width:
> allOf:
> - $ref: /schemas/types.yaml#/definitions/uint32
> @@ -132,6 +137,11 @@ patternProperties:
> description:
> Delay, in microseconds, after a write transfer.
>
> + spi-tx-dtr:
> + $ref: /schemas/types.yaml#/definitions/flag
> + description:
> + Device supports transmitting in DTR mode.
> +
> required:
> - compatible
> - reg

2020-02-27 16:29:04

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH v2 01/11] dt-bindings: spi: allow expressing DTR capability

On Thu, Feb 27, 2020 at 05:11:47PM +0100, Boris Brezillon wrote:
> Pratyush Yadav <[email protected]> wrote:

> > Allow spi devices to express DTR receive and transmit capabilities via
> > the properties "spi-rx-dtr" and "spi-tx-dtr".

> Is the RX/TX granularity really useful?

It's what we do for other properties, and if this is anything like the
other things adding extra wiring you can't assume that the ability to
use the feature for TX implies RX.


Attachments:
(No filename) (463.00 B)
signature.asc (499.00 B)
Download all attachments

2020-02-27 16:31:06

by Geert Uytterhoeven

[permalink] [raw]
Subject: Re: [PATCH v2 01/11] dt-bindings: spi: allow expressing DTR capability

Hi Pratyush,

On Wed, Feb 26, 2020 at 10:37 AM Pratyush Yadav <[email protected]> wrote:
> Allow spi devices to express DTR receive and transmit capabilities via
> the properties "spi-rx-dtr" and "spi-tx-dtr".
>
> Signed-off-by: Pratyush Yadav <[email protected]>

Thanks for your patch!

> --- a/Documentation/devicetree/bindings/spi/spi-controller.yaml
> +++ b/Documentation/devicetree/bindings/spi/spi-controller.yaml
> @@ -120,6 +120,11 @@ patternProperties:
> description:
> Delay, in microseconds, after a read transfer.
>
> + spi-rx-dtr:
> + $ref: /schemas/types.yaml#/definitions/flag
> + description:
> + Device supports receiving in DTR mode.

Please explain "DTR" in the document, at least once, e.g.

Double Transfer Rate (DTR).

Gr{oetje,eeting}s,

Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected]

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds

2020-02-27 16:37:41

by Boris Brezillon

[permalink] [raw]
Subject: Re: [PATCH v2 03/11] spi: spi-mem: allow specifying whether an op is DTR or not

On Wed, 26 Feb 2020 15:06:55 +0530
Pratyush Yadav <[email protected]> wrote:

> Each phase is given a separate 'is_dtr' field so mixed protocols like
> 4S-4D-4D can be supported.
>
> Also add the mode bits SPI_RX_DTR and SPI_TX_DTR so controllers can
> specify whether they support DTR modes or not.
>
> Signed-off-by: Pratyush Yadav <[email protected]>
> ---
> drivers/spi/spi-mem.c | 23 +++++++++++++++++++++++
> include/linux/spi/spi-mem.h | 8 ++++++++
> 2 files changed, 31 insertions(+)
>
> diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c
> index e5a46f0eb93b..cb13e0878b95 100644
> --- a/drivers/spi/spi-mem.c
> +++ b/drivers/spi/spi-mem.c
> @@ -99,6 +99,16 @@ void spi_controller_dma_unmap_mem_op_data(struct spi_controller *ctlr,
> }
> EXPORT_SYMBOL_GPL(spi_controller_dma_unmap_mem_op_data);
>
> +static int spi_check_dtr_req(struct spi_mem *mem, bool tx)
> +{
> + u32 mode = mem->spi->mode;
> +
> + if ((tx && (mode & SPI_TX_DTR)) || (!tx && (mode & SPI_RX_DTR)))
> + return 0;
> +
> + return -ENOTSUPP;
> +}
> +
> static int spi_check_buswidth_req(struct spi_mem *mem, u8 buswidth, bool tx)
> {
> u32 mode = mem->spi->mode;
> @@ -154,6 +164,19 @@ bool spi_mem_default_supports_op(struct spi_mem *mem,
> op->data.dir == SPI_MEM_DATA_OUT))
> return false;
>
> + if (op->cmd.is_dtr && spi_check_dtr_req(mem, true))
> + return false;
> +
> + if (op->addr.is_dtr && spi_check_dtr_req(mem, true))
> + return false;
> +
> + if (op->dummy.is_dtr && spi_check_dtr_req(mem, true))
> + return false;
> +
> + if (op->data.dir != SPI_MEM_NO_DATA && op->data.is_dtr &&
> + spi_check_dtr_req(mem, op->data.dir == SPI_MEM_DATA_OUT))
> + return false;
> +

Not all controllers use spi_mem_default_supports_op(). Those should be
patched to reject DTR ops too.

> return true;
> }
> EXPORT_SYMBOL_GPL(spi_mem_default_supports_op);
> diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h
> index af9ff2f0f1b2..4669082b4e3b 100644
> --- a/include/linux/spi/spi-mem.h
> +++ b/include/linux/spi/spi-mem.h
> @@ -71,6 +71,7 @@ enum spi_mem_data_dir {
> * struct spi_mem_op - describes a SPI memory operation
> * @cmd.buswidth: number of IO lines used to transmit the command
> * @cmd.opcode: operation opcode
> + * @cmd.is_dtr: whether the command opcode should be sent in DTR mode or not
> * @addr.nbytes: number of address bytes to send. Can be zero if the operation
> * does not need to send an address
> * @addr.buswidth: number of IO lines used to transmit the address cycles
> @@ -78,10 +79,13 @@ enum spi_mem_data_dir {
> * Note that only @addr.nbytes are taken into account in this
> * address value, so users should make sure the value fits in the
> * assigned number of bytes.
> + * @addr.is_dtr: whether the address should be sent in DTR mode or not
> * @dummy.nbytes: number of dummy bytes to send after an opcode or address. Can
> * be zero if the operation does not require dummy bytes
> * @dummy.buswidth: number of IO lanes used to transmit the dummy bytes
> + * @dummy.is_dtr: whether the dummy bytes should be sent in DTR mode or not
> * @data.buswidth: number of IO lanes used to send/receive the data
> + * @data.is_dtr: whether the data should be sent in DTR mode or not
> * @data.dir: direction of the transfer
> * @data.nbytes: number of data bytes to send/receive. Can be zero if the
> * operation does not involve transferring data
> @@ -92,21 +96,25 @@ struct spi_mem_op {
> struct {
> u8 buswidth;
> u8 opcode;
> + bool is_dtr;

Hm, maybe use a bitfield here so we can pack other fields if needed.
Also not convince the 'is_' prefix is useful.

u8 dtr : 1;

> } cmd;
>
> struct {
> u8 nbytes;
> u8 buswidth;

Maybe move the dtr field here so the compiler can pack things instead of
adding extra padding for the u64 alignment.

u8 dtr : 1;

> u64 val;
> + bool is_dtr;
> } addr;
>
> struct {
> u8 nbytes;
> u8 buswidth;
> + bool is_dtr;
> } dummy;
>
> struct {
> u8 buswidth;
> + bool is_dtr;
> enum spi_mem_data_dir dir;
> unsigned int nbytes;
> union {

2020-02-27 16:41:05

by Geert Uytterhoeven

[permalink] [raw]
Subject: Re: [PATCH v2 01/11] dt-bindings: spi: allow expressing DTR capability

Hi Mark,

On Thu, Feb 27, 2020 at 5:28 PM Mark Brown <[email protected]> wrote:
> On Thu, Feb 27, 2020 at 05:11:47PM +0100, Boris Brezillon wrote:
> > Pratyush Yadav <[email protected]> wrote:
>
> > > Allow spi devices to express DTR receive and transmit capabilities via
> > > the properties "spi-rx-dtr" and "spi-tx-dtr".
>
> > Is the RX/TX granularity really useful?
>
> It's what we do for other properties, and if this is anything like the
> other things adding extra wiring you can't assume that the ability to
> use the feature for TX implies RX.

Double Transfer Rate uses the same wire.
But as you sample at both the rising and the falling edges of the clock, this
makes the cpha setting meaningless for such transfers, I think ;-)

However, as the future may bring us QDR, perhaps this should not be a
boolean flag, but an integer value?
Cfr. spi-tx-bus-width vs. the original spi-tx-dual/spi-tx-quad proposal.

What would be a good name (as we only need one)? spi-data-phases?

Gr{oetje,eeting}s,

Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected]

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds

2020-02-27 16:44:43

by Boris Brezillon

[permalink] [raw]
Subject: Re: [PATCH v2 04/11] spi: spi-mem: allow specifying a command's extension

On Wed, 26 Feb 2020 15:06:56 +0530
Pratyush Yadav <[email protected]> wrote:

> In xSPI mode, flashes expect 2-byte opcodes. The second byte is called
> the "command extension". There can be 3 types of extensions in xSPI:
> repeat, invert, and hex. When the extension type is "repeat", the same
> opcode is sent twice. When it is "invert", the second byte is the
> inverse of the opcode. When it is "hex" an additional opcode byte based
> is sent with the command whose value can be anything.
>
> Signed-off-by: Pratyush Yadav <[email protected]>
> ---
> drivers/spi/spi-mem.c | 23 +++++++++++++++++++++++
> include/linux/spi/spi-mem.h | 24 ++++++++++++++++++++++++
> 2 files changed, 47 insertions(+)
>
> diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c
> index cb13e0878b95..3838ddc9aeec 100644
> --- a/drivers/spi/spi-mem.c
> +++ b/drivers/spi/spi-mem.c
> @@ -462,6 +462,29 @@ int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
> }
> EXPORT_SYMBOL_GPL(spi_mem_adjust_op_size);
>
> +int spi_mem_get_cmd_ext(const struct spi_mem_op *op, u8 *ext)
> +{
> + switch (op->cmd.ext_type) {
> + case SPI_MEM_EXT_INVERT:
> + *ext = ~op->cmd.opcode;
> + break;
> +
> + case SPI_MEM_EXT_REPEAT:
> + *ext = op->cmd.opcode;
> + break;
> +
> + case SPI_MEM_EXT_HEX:
> + *ext = op->cmd.ext;
> + break;
> +
> + default:
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(spi_mem_get_cmd_ext);
> +
> static ssize_t spi_mem_no_dirmap_read(struct spi_mem_dirmap_desc *desc,
> u64 offs, size_t len, void *buf)
> {
> diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h
> index 4669082b4e3b..06ccab17e4d0 100644
> --- a/include/linux/spi/spi-mem.h
> +++ b/include/linux/spi/spi-mem.h
> @@ -67,11 +67,31 @@ enum spi_mem_data_dir {
> SPI_MEM_DATA_OUT,
> };
>
> +/**
> + * enum spi_mem_cmd_ext - describes the command opcode extension in DTR mode
> + * @SPI_MEM_EXT_NONE: no extension. This is the default, and is used in Legacy
> + * SPI mode
> + * @SPI_MEM_EXT_REPEAT: the extension is same as the opcode
> + * @SPI_MEM_EXT_INVERT: the extension is the bitwise inverse of the opcode
> + * @SPI_MEM_EXT_HEX: the extension is any hex value. The command and opcode
> + * combine to form a 16-bit opcode.
> + */
> +enum spi_mem_cmd_ext {
> + SPI_MEM_EXT_NONE = 0,
> + SPI_MEM_EXT_REPEAT,
> + SPI_MEM_EXT_INVERT,
> + SPI_MEM_EXT_HEX,
> +};
> +
> /**
> * struct spi_mem_op - describes a SPI memory operation
> * @cmd.buswidth: number of IO lines used to transmit the command
> * @cmd.opcode: operation opcode
> * @cmd.is_dtr: whether the command opcode should be sent in DTR mode or not
> + * @cmd.ext_type: type of the command opcode extension in DTR mode
> + * @cmd.ext: value of the command opcode extension in DTR mode. It is
> + * only set when 'ext_type' is 'SPI_MEM_EXT_HEX'. In all other
> + * cases, the extension can be directly derived from the opcode.
> * @addr.nbytes: number of address bytes to send. Can be zero if the operation
> * does not need to send an address
> * @addr.buswidth: number of IO lines used to transmit the address cycles
> @@ -97,6 +117,8 @@ struct spi_mem_op {
> u8 buswidth;
> u8 opcode;
> bool is_dtr;
> + enum spi_mem_cmd_ext ext_type;
> + u8 ext;

Could we instead make opcode an u16 (or u8[2]) and pass the number of
bytes, as done for the other addr? Mode can be extracted from the
opcode/nbytes values if really needed, and the caller would be
responsible for filling those fields properly (which shouldn't be too
hard)

> } cmd;
>
> struct {
> @@ -361,6 +383,8 @@ int spi_mem_driver_register_with_owner(struct spi_mem_driver *drv,
>
> void spi_mem_driver_unregister(struct spi_mem_driver *drv);
>
> +int spi_mem_get_cmd_ext(const struct spi_mem_op *op, u8 *ext);
> +
> #define spi_mem_driver_register(__drv) \
> spi_mem_driver_register_with_owner(__drv, THIS_MODULE)
>

2020-02-27 16:44:53

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH v2 01/11] dt-bindings: spi: allow expressing DTR capability

On Thu, Feb 27, 2020 at 05:40:31PM +0100, Geert Uytterhoeven wrote:
> On Thu, Feb 27, 2020 at 5:28 PM Mark Brown <[email protected]> wrote:

> > It's what we do for other properties, and if this is anything like the
> > other things adding extra wiring you can't assume that the ability to
> > use the feature for TX implies RX.

> Double Transfer Rate uses the same wire.

But is it still on either the TX or RX signals?

> But as you sample at both the rising and the falling edges of the clock, this
> makes the cpha setting meaningless for such transfers, I think ;-)

Might affect what the first bit is possibly?

> However, as the future may bring us QDR, perhaps this should not be a
> boolean flag, but an integer value?
> Cfr. spi-tx-bus-width vs. the original spi-tx-dual/spi-tx-quad proposal.

> What would be a good name (as we only need one)? spi-data-phases?

Sounds reasonable, apart from the increasingly vague connection with
something that's recognizably SPI :P


Attachments:
(No filename) (0.98 kB)
signature.asc (499.00 B)
Download all attachments

2020-02-27 16:59:06

by Boris Brezillon

[permalink] [raw]
Subject: Re: [PATCH v2 06/11] mtd: spi-nor: add support for DTR protocol

On Wed, 26 Feb 2020 15:06:58 +0530
Pratyush Yadav <[email protected]> wrote:

> Double Transfer Rate (DTR) is SPI protocol in which data is transferred
> on each clock edge as opposed to on each clock cycle. Make
> framework-level changes to allow supporting flashes in DTR mode.
>
> Right now, mixed DTR modes are not supported. So, for example a mode
> like 4S-4D-4D will not work. All phases need to be either DTR or STR.

Didn't go deep into the patch but at first glance you don't seem to
extend the framework to support stateful modes as I tried to do here
[1]. That's really something we should address before considering
supporting xD-xD-xD modes, unless the SPI-NOR only supports one
stateful mode. If we don't do that first, we might face all sort of
unpleasant issues:

* kexec not working correctly because the previous kernel left the NOR
in an unknown state
* suspend/resume not working properly
* linux not booting properly because the bootloader left the device in
its non-default mode
* ...

[1]https://patchwork.kernel.org/cover/10638055/

2020-02-27 17:05:50

by Geert Uytterhoeven

[permalink] [raw]
Subject: Re: [PATCH v2 01/11] dt-bindings: spi: allow expressing DTR capability

Hi Mark,

On Thu, Feb 27, 2020 at 5:44 PM Mark Brown <[email protected]> wrote:
> On Thu, Feb 27, 2020 at 05:40:31PM +0100, Geert Uytterhoeven wrote:
> > On Thu, Feb 27, 2020 at 5:28 PM Mark Brown <[email protected]> wrote:
> > > It's what we do for other properties, and if this is anything like the
> > > other things adding extra wiring you can't assume that the ability to
> > > use the feature for TX implies RX.
>
> > Double Transfer Rate uses the same wire.
>
> But is it still on either the TX or RX signals?

E.g. good old Spansion S25FL512S supports single/dual/quad DDR, but
apparently only for read, not write.
Other FLASHes may support both directions. I guess.

> > But as you sample at both the rising and the falling edges of the clock, this
> > makes the cpha setting meaningless for such transfers, I think ;-)
>
> Might affect what the first bit is possibly?

Quoting the manual for the above part:

4.1.2
Double Data Rate (DDR)
Mode 0 and Mode 3 are also supported for DDR commands. In DDR
commands, the instruction bits are
always latched on the rising edge of clock, the same as in SDR
commands. However, the address and input
data that follow the instruction are latched on both the rising and
falling edges of SCK. The first address bit is
latched on the first rising edge of SCK following the falling edge at
the end of the last instruction bit. The first
bit of output data is driven on the falling edge at the end of the
last access latency (dummy) cycle.
SCK cycles are measured (counted) in the same way as in SDR commands,
from one falling edge of SCK to
the next falling edge of SCK. In mode 0 the beginning of the first SCK
cycle in a command is measured from
the falling edge of CS# to the first falling edge of SCK because SCK
is already low at the beginning of a
command.

> > However, as the future may bring us QDR, perhaps this should not be a
> > boolean flag, but an integer value?
> > Cfr. spi-tx-bus-width vs. the original spi-tx-dual/spi-tx-quad proposal.
>
> > What would be a good name (as we only need one)? spi-data-phases?
>
> Sounds reasonable, apart from the increasingly vague connection with
> something that's recognizably SPI :P

Yeah, especially Octal and Hyper modes are far from serial ;-)

Gr{oetje,eeting}s,

Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected]

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds

2020-02-27 17:07:32

by Boris Brezillon

[permalink] [raw]
Subject: Re: [PATCH v2 01/11] dt-bindings: spi: allow expressing DTR capability

On Thu, 27 Feb 2020 16:44:25 +0000
Mark Brown <[email protected]> wrote:

> On Thu, Feb 27, 2020 at 05:40:31PM +0100, Geert Uytterhoeven wrote:
> > On Thu, Feb 27, 2020 at 5:28 PM Mark Brown <[email protected]> wrote:
>
> > > It's what we do for other properties, and if this is anything like the
> > > other things adding extra wiring you can't assume that the ability to
> > > use the feature for TX implies RX.
>
> > Double Transfer Rate uses the same wire.
>
> But is it still on either the TX or RX signals?

There's no separate RX/TX pins when using xD-xD-xD modes (pins switch
from RX to TX) and I doubt DTR will ever be used on single SPI.

>
> > But as you sample at both the rising and the falling edges of the clock, this
> > makes the cpha setting meaningless for such transfers, I think ;-)
>
> Might affect what the first bit is possibly?
>
> > However, as the future may bring us QDR, perhaps this should not be a
> > boolean flag, but an integer value?
> > Cfr. spi-tx-bus-width vs. the original spi-tx-dual/spi-tx-quad proposal.
>
> > What would be a good name (as we only need one)? spi-data-phases?
>
> Sounds reasonable, apart from the increasingly vague connection with
> something that's recognizably SPI :P

Or maybe we should refrain from adding a new flag and wait a bit to see
if this DTR mode is actually used for regular SPI transfers (AKA not
spi-mem) :-).

2020-02-28 09:38:00

by Pratyush Yadav

[permalink] [raw]
Subject: Re: [PATCH v2 06/11] mtd: spi-nor: add support for DTR protocol

Hi Boris,

On 27/02/20 05:58PM, Boris Brezillon wrote:
> On Wed, 26 Feb 2020 15:06:58 +0530
> Pratyush Yadav <[email protected]> wrote:
>
> > Double Transfer Rate (DTR) is SPI protocol in which data is transferred
> > on each clock edge as opposed to on each clock cycle. Make
> > framework-level changes to allow supporting flashes in DTR mode.
> >
> > Right now, mixed DTR modes are not supported. So, for example a mode
> > like 4S-4D-4D will not work. All phases need to be either DTR or STR.
>
> Didn't go deep into the patch but at first glance you don't seem to
> extend the framework to support stateful modes as I tried to do here
> [1]. That's really something we should address before considering
> supporting xD-xD-xD modes, unless the SPI-NOR only supports one
> stateful mode. If we don't do that first, we might face all sort of
> unpleasant issues:
>
> * kexec not working correctly because the previous kernel left the NOR
> in an unknown state
> * suspend/resume not working properly
> * linux not booting properly because the bootloader left the device in
> its non-default mode
> * ...

Correct. I am working on a follow-up series that takes care of these
problems. The series will allow spi-nor to detect what mode the flash is
in and then run the SFPD procedure in that mode (or maybe switch to
single SPI mode and then go about its business as usual? I haven't
figured out all the details yet).

So for the context of this series, assume we are handed the flash in
single SPI mode.

> [1]https://patchwork.kernel.org/cover/10638055/

BTW, I took a quick look at this series but I don't see any code that
tries to detect which mode the flash is in (which is the troublesome
part [0]). So, for example, if the bootloader leaves the flash in
8D-8D-8D mode, how would your series handle that situation?

[0] There are multiple problems to take care of when trying to detect
which mode a flash is in. We can try reading SFDP in each mode and
whichever mode gives us the correct "SFDP" signature is the mode the
flash is in. But the problem is that even in xSPI standard Read SFDP
command is optional in 8D-8D-8D mode, let alone non-xSPI flashes.
Another problem is that the address bytes and dummy cycles for Read
SFDP are not the same for every flash. The xSPI standard says
address bytes can be 3/4 and dummy cycles can be 8/20. So, for
example, Cypress s28hs/s28ht family and Micron Xccela (mt35x) family
use 4 address bytes, but the Adesto ATXP032/ATXP032R flashes use 3
address bytes.

Say that a flash supports Read SFDP in 8D-8D-8D mode and we try all
the combinations to find out which mode the flash is in, we now have
the problem of actually identifying the flash. Unfortunately, the
Read ID command is not uniform across flash vendors. The Micron
Xccela flashes use 8 dummy cycles and no address bytes for Read ID.
The Cypress s28hs/t family uses configurable dummy cycles
(defaulting to 3) and needs 4 dummy address bytes all of which are
0.

If we can't find out which flash it is, we can't run its fixup
hooks, and might end up running it with incorrect settings. And all
this is assuming a flash even has SFDP and has it available in all
modes.

So, the only solution I can now think of is having the flash name in
its compatible string in the device tree. This way we can skip all
the Read ID ugliness and can have flash-specific hooks to make it
easier to detect the mode it is in (though I wonder if it is even
possible to detect the mode in a flash that doesn't have SFDP in
8D-8D-8D).

Thoughts? Is there a better way to solve this problem that I didn't
think of?

--
Regards,
Pratyush Yadav
Texas Instruments India

2020-02-28 09:42:28

by Pratyush Yadav

[permalink] [raw]
Subject: Re: [PATCH v2 04/11] spi: spi-mem: allow specifying a command's extension

On 27/02/20 05:44PM, Boris Brezillon wrote:
> On Wed, 26 Feb 2020 15:06:56 +0530
> Pratyush Yadav <[email protected]> wrote:
>
> > In xSPI mode, flashes expect 2-byte opcodes. The second byte is called
> > the "command extension". There can be 3 types of extensions in xSPI:
> > repeat, invert, and hex. When the extension type is "repeat", the same
> > opcode is sent twice. When it is "invert", the second byte is the
> > inverse of the opcode. When it is "hex" an additional opcode byte based
> > is sent with the command whose value can be anything.
> >
> > Signed-off-by: Pratyush Yadav <[email protected]>
> > ---
> > drivers/spi/spi-mem.c | 23 +++++++++++++++++++++++
> > include/linux/spi/spi-mem.h | 24 ++++++++++++++++++++++++
> > 2 files changed, 47 insertions(+)
> >
> > diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c
> > index cb13e0878b95..3838ddc9aeec 100644
> > --- a/drivers/spi/spi-mem.c
> > +++ b/drivers/spi/spi-mem.c
> > @@ -462,6 +462,29 @@ int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
> > }
> > EXPORT_SYMBOL_GPL(spi_mem_adjust_op_size);
> >
> > +int spi_mem_get_cmd_ext(const struct spi_mem_op *op, u8 *ext)
> > +{
> > + switch (op->cmd.ext_type) {
> > + case SPI_MEM_EXT_INVERT:
> > + *ext = ~op->cmd.opcode;
> > + break;
> > +
> > + case SPI_MEM_EXT_REPEAT:
> > + *ext = op->cmd.opcode;
> > + break;
> > +
> > + case SPI_MEM_EXT_HEX:
> > + *ext = op->cmd.ext;
> > + break;
> > +
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + return 0;
> > +}
> > +EXPORT_SYMBOL_GPL(spi_mem_get_cmd_ext);
> > +
> > static ssize_t spi_mem_no_dirmap_read(struct spi_mem_dirmap_desc *desc,
> > u64 offs, size_t len, void *buf)
> > {
> > diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h
> > index 4669082b4e3b..06ccab17e4d0 100644
> > --- a/include/linux/spi/spi-mem.h
> > +++ b/include/linux/spi/spi-mem.h
> > @@ -67,11 +67,31 @@ enum spi_mem_data_dir {
> > SPI_MEM_DATA_OUT,
> > };
> >
> > +/**
> > + * enum spi_mem_cmd_ext - describes the command opcode extension in DTR mode
> > + * @SPI_MEM_EXT_NONE: no extension. This is the default, and is used in Legacy
> > + * SPI mode
> > + * @SPI_MEM_EXT_REPEAT: the extension is same as the opcode
> > + * @SPI_MEM_EXT_INVERT: the extension is the bitwise inverse of the opcode
> > + * @SPI_MEM_EXT_HEX: the extension is any hex value. The command and opcode
> > + * combine to form a 16-bit opcode.
> > + */
> > +enum spi_mem_cmd_ext {
> > + SPI_MEM_EXT_NONE = 0,
> > + SPI_MEM_EXT_REPEAT,
> > + SPI_MEM_EXT_INVERT,
> > + SPI_MEM_EXT_HEX,
> > +};
> > +
> > /**
> > * struct spi_mem_op - describes a SPI memory operation
> > * @cmd.buswidth: number of IO lines used to transmit the command
> > * @cmd.opcode: operation opcode
> > * @cmd.is_dtr: whether the command opcode should be sent in DTR mode or not
> > + * @cmd.ext_type: type of the command opcode extension in DTR mode
> > + * @cmd.ext: value of the command opcode extension in DTR mode. It is
> > + * only set when 'ext_type' is 'SPI_MEM_EXT_HEX'. In all other
> > + * cases, the extension can be directly derived from the opcode.
> > * @addr.nbytes: number of address bytes to send. Can be zero if the operation
> > * does not need to send an address
> > * @addr.buswidth: number of IO lines used to transmit the address cycles
> > @@ -97,6 +117,8 @@ struct spi_mem_op {
> > u8 buswidth;
> > u8 opcode;
> > bool is_dtr;
> > + enum spi_mem_cmd_ext ext_type;
> > + u8 ext;
>
> Could we instead make opcode an u16 (or u8[2]) and pass the number of
> bytes, as done for the other addr? Mode can be extracted from the
> opcode/nbytes values if really needed, and the caller would be
> responsible for filling those fields properly (which shouldn't be too
> hard)

Ok. Will do.

> > } cmd;
> >
> > struct {
> > @@ -361,6 +383,8 @@ int spi_mem_driver_register_with_owner(struct spi_mem_driver *drv,
> >
> > void spi_mem_driver_unregister(struct spi_mem_driver *drv);
> >
> > +int spi_mem_get_cmd_ext(const struct spi_mem_op *op, u8 *ext);
> > +
> > #define spi_mem_driver_register(__drv) \
> > spi_mem_driver_register_with_owner(__drv, THIS_MODULE)
> >
>

--
Regards,
Pratyush Yadav
Texas Instruments India

2020-02-28 09:46:49

by Pratyush Yadav

[permalink] [raw]
Subject: Re: [PATCH v2 01/11] dt-bindings: spi: allow expressing DTR capability

Hi Geert,

On 27/02/20 05:29PM, Geert Uytterhoeven wrote:
> Hi Pratyush,
>
> On Wed, Feb 26, 2020 at 10:37 AM Pratyush Yadav <[email protected]> wrote:
> > Allow spi devices to express DTR receive and transmit capabilities via
> > the properties "spi-rx-dtr" and "spi-tx-dtr".
> >
> > Signed-off-by: Pratyush Yadav <[email protected]>
>
> Thanks for your patch!
>
> > --- a/Documentation/devicetree/bindings/spi/spi-controller.yaml
> > +++ b/Documentation/devicetree/bindings/spi/spi-controller.yaml
> > @@ -120,6 +120,11 @@ patternProperties:
> > description:
> > Delay, in microseconds, after a read transfer.
> >
> > + spi-rx-dtr:
> > + $ref: /schemas/types.yaml#/definitions/flag
> > + description:
> > + Device supports receiving in DTR mode.
>
> Please explain "DTR" in the document, at least once, e.g.
>
> Double Transfer Rate (DTR).

Will do.

> Gr{oetje,eeting}s,
>
> Geert
>
> --
> Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected]
>
> In personal conversations with technical people, I call myself a hacker. But
> when I'm talking to journalists I just say "programmer" or something like that.
> -- Linus Torvalds

--
Regards,
Pratyush Yadav
Texas Instruments India

2020-02-28 10:54:23

by Boris Brezillon

[permalink] [raw]
Subject: Re: [PATCH v2 06/11] mtd: spi-nor: add support for DTR protocol

On Fri, 28 Feb 2020 15:06:58 +0530
Pratyush Yadav <[email protected]> wrote:

> Hi Boris,
>
> On 27/02/20 05:58PM, Boris Brezillon wrote:
> > On Wed, 26 Feb 2020 15:06:58 +0530
> > Pratyush Yadav <[email protected]> wrote:
> >
> > > Double Transfer Rate (DTR) is SPI protocol in which data is transferred
> > > on each clock edge as opposed to on each clock cycle. Make
> > > framework-level changes to allow supporting flashes in DTR mode.
> > >
> > > Right now, mixed DTR modes are not supported. So, for example a mode
> > > like 4S-4D-4D will not work. All phases need to be either DTR or STR.
> >
> > Didn't go deep into the patch but at first glance you don't seem to
> > extend the framework to support stateful modes as I tried to do here
> > [1]. That's really something we should address before considering
> > supporting xD-xD-xD modes, unless the SPI-NOR only supports one
> > stateful mode. If we don't do that first, we might face all sort of
> > unpleasant issues:
> >
> > * kexec not working correctly because the previous kernel left the NOR
> > in an unknown state
> > * suspend/resume not working properly
> > * linux not booting properly because the bootloader left the device in
> > its non-default mode
> > * ...
>
> Correct. I am working on a follow-up series that takes care of these
> problems. The series will allow spi-nor to detect what mode the flash is
> in and then run the SFPD procedure in that mode (or maybe switch to
> single SPI mode and then go about its business as usual? I haven't
> figured out all the details yet).
>
> So for the context of this series, assume we are handed the flash in
> single SPI mode.
>
> > [1]https://patchwork.kernel.org/cover/10638055/
>
> BTW, I took a quick look at this series but I don't see any code that
> tries to detect which mode the flash is in (which is the troublesome
> part [0]). So, for example, if the bootloader leaves the flash in
> 8D-8D-8D mode, how would your series handle that situation?

Oh, it's definitely not taking care of that, it was just paving the
road for spi-nor state tracking. You'd need to extend it to support
8D-8D-8D to 1-1-1 transitions at boot time (if that's even possible).

>
> [0] There are multiple problems to take care of when trying to detect
> which mode a flash is in. We can try reading SFDP in each mode and
> whichever mode gives us the correct "SFDP" signature is the mode the
> flash is in. But the problem is that even in xSPI standard Read SFDP
> command is optional in 8D-8D-8D mode, let alone non-xSPI flashes.
> Another problem is that the address bytes and dummy cycles for Read
> SFDP are not the same for every flash. The xSPI standard says
> address bytes can be 3/4 and dummy cycles can be 8/20. So, for
> example, Cypress s28hs/s28ht family and Micron Xccela (mt35x) family
> use 4 address bytes, but the Adesto ATXP032/ATXP032R flashes use 3
> address bytes.

I'd rather go with something simpler and more widely supported than SFDP
reads. Don't we have a simple command that's supported by all flashes
and returns well known data. Isn't there an EXIT sequence that allows
NORs to return to a single SPI state?

>
> Say that a flash supports Read SFDP in 8D-8D-8D mode and we try all
> the combinations to find out which mode the flash is in, we now have
> the problem of actually identifying the flash. Unfortunately, the
> Read ID command is not uniform across flash vendors. The Micron
> Xccela flashes use 8 dummy cycles and no address bytes for Read ID.
> The Cypress s28hs/t family uses configurable dummy cycles
> (defaulting to 3) and needs 4 dummy address bytes all of which are
> 0.

Yep, that's what I complained about when I tried to support the
Macronix flash. They didn't plan for a reliable RETURN-TO-SINGLE-SPI
sequence which would not conflict with any other existing SPI commands,
and that's a real problem.

>
> If we can't find out which flash it is, we can't run its fixup
> hooks, and might end up running it with incorrect settings. And all
> this is assuming a flash even has SFDP and has it available in all
> modes.

Absolutely.

>
> So, the only solution I can now think of is having the flash name in
> its compatible string in the device tree. This way we can skip all
> the Read ID ugliness and can have flash-specific hooks to make it
> easier to detect the mode it is in (though I wonder if it is even
> possible to detect the mode in a flash that doesn't have SFDP in
> 8D-8D-8D).

Hm, I'd really like to avoid that if possible.

>
> Thoughts? Is there a better way to solve this problem that I didn't
> think of?
>

Nope, except maybe mandate that the bootloader always put the NOR in
single SPI mode before booting Linux (and Linux should do the same,
which is what my series was trying to address IIRC).

2020-02-28 12:08:29

by Pratyush Yadav

[permalink] [raw]
Subject: Re: [PATCH v2 06/11] mtd: spi-nor: add support for DTR protocol

On 28/02/20 11:53AM, Boris Brezillon wrote:
> On Fri, 28 Feb 2020 15:06:58 +0530
> Pratyush Yadav <[email protected]> wrote:
>
> > Hi Boris,
> >
> > On 27/02/20 05:58PM, Boris Brezillon wrote:
> > > On Wed, 26 Feb 2020 15:06:58 +0530
> > > Pratyush Yadav <[email protected]> wrote:
> > >
> > > > Double Transfer Rate (DTR) is SPI protocol in which data is transferred
> > > > on each clock edge as opposed to on each clock cycle. Make
> > > > framework-level changes to allow supporting flashes in DTR mode.
> > > >
> > > > Right now, mixed DTR modes are not supported. So, for example a mode
> > > > like 4S-4D-4D will not work. All phases need to be either DTR or STR.
> > >
> > > Didn't go deep into the patch but at first glance you don't seem to
> > > extend the framework to support stateful modes as I tried to do here
> > > [1]. That's really something we should address before considering
> > > supporting xD-xD-xD modes, unless the SPI-NOR only supports one
> > > stateful mode. If we don't do that first, we might face all sort of
> > > unpleasant issues:
> > >
> > > * kexec not working correctly because the previous kernel left the NOR
> > > in an unknown state
> > > * suspend/resume not working properly
> > > * linux not booting properly because the bootloader left the device in
> > > its non-default mode
> > > * ...
> >
> > Correct. I am working on a follow-up series that takes care of these
> > problems. The series will allow spi-nor to detect what mode the flash is
> > in and then run the SFPD procedure in that mode (or maybe switch to
> > single SPI mode and then go about its business as usual? I haven't
> > figured out all the details yet).
> >
> > So for the context of this series, assume we are handed the flash in
> > single SPI mode.
> >
> > > [1]https://patchwork.kernel.org/cover/10638055/
> >
> > BTW, I took a quick look at this series but I don't see any code that
> > tries to detect which mode the flash is in (which is the troublesome
> > part [0]). So, for example, if the bootloader leaves the flash in
> > 8D-8D-8D mode, how would your series handle that situation?
>
> Oh, it's definitely not taking care of that, it was just paving the
> road for spi-nor state tracking. You'd need to extend it to support
> 8D-8D-8D to 1-1-1 transitions at boot time (if that's even possible).
>
> >
> > [0] There are multiple problems to take care of when trying to detect
> > which mode a flash is in. We can try reading SFDP in each mode and
> > whichever mode gives us the correct "SFDP" signature is the mode the
> > flash is in. But the problem is that even in xSPI standard Read SFDP
> > command is optional in 8D-8D-8D mode, let alone non-xSPI flashes.
> > Another problem is that the address bytes and dummy cycles for Read
> > SFDP are not the same for every flash. The xSPI standard says
> > address bytes can be 3/4 and dummy cycles can be 8/20. So, for
> > example, Cypress s28hs/s28ht family and Micron Xccela (mt35x) family
> > use 4 address bytes, but the Adesto ATXP032/ATXP032R flashes use 3
> > address bytes.
>
> I'd rather go with something simpler and more widely supported than SFDP
> reads. Don't we have a simple command that's supported by all flashes
> and returns well known data.

I'm not aware of any other command that would return well-known data.

> Isn't there an EXIT sequence that allows NORs to return to a single
> SPI state?

Yes there is, but it comes with a lot of strings attached. There is a
hardware reset pin on some flashes that puts the flash in Power-on-Reset
(POR) mode. But that pin is not mandatory. It also might not be
connected on a given board.

The other option is a "Soft Reset" (also optional), which puts the flash
in POR mode after it is given the soft reset command. But to send the
command you need to know the mode the device is in. On top of that, the
Soft Reset opcode differs between flashes. According to the xSPI spec,
some flashes can have the opcode as 0xF0 and some others can have it as
a two command sequence of 0x66 and 0x99.

And the cherry on top is the fact that these reset operations return to
a state based on the value of the non-volatile bits. So, if the
non-volatile configuration is 8D-8D-8D mode, then all these resets
achieve nothing.

> >
> > Say that a flash supports Read SFDP in 8D-8D-8D mode and we try all
> > the combinations to find out which mode the flash is in, we now have
> > the problem of actually identifying the flash. Unfortunately, the
> > Read ID command is not uniform across flash vendors. The Micron
> > Xccela flashes use 8 dummy cycles and no address bytes for Read ID.
> > The Cypress s28hs/t family uses configurable dummy cycles
> > (defaulting to 3) and needs 4 dummy address bytes all of which are
> > 0.
>
> Yep, that's what I complained about when I tried to support the
> Macronix flash. They didn't plan for a reliable RETURN-TO-SINGLE-SPI
> sequence which would not conflict with any other existing SPI commands,
> and that's a real problem.
>
> >
> > If we can't find out which flash it is, we can't run its fixup
> > hooks, and might end up running it with incorrect settings. And all
> > this is assuming a flash even has SFDP and has it available in all
> > modes.
>
> Absolutely.
>
> >
> > So, the only solution I can now think of is having the flash name in
> > its compatible string in the device tree. This way we can skip all
> > the Read ID ugliness and can have flash-specific hooks to make it
> > easier to detect the mode it is in (though I wonder if it is even
> > possible to detect the mode in a flash that doesn't have SFDP in
> > 8D-8D-8D).
>
> Hm, I'd really like to avoid that if possible.

Unfortunately, I don't really see a better alternative. Just so I
understand this better, why do you think it is something worth avoiding?

> >
> > Thoughts? Is there a better way to solve this problem that I didn't
> > think of?
> >
>
> Nope, except maybe mandate that the bootloader always put the NOR in
> single SPI mode before booting Linux (and Linux should do the same,
> which is what my series was trying to address IIRC).

A simple bootloader might not even have a SPI driver. So, if the flash
PORs to 8D-8D-8D, Linux would be unable to use the flash.

Or, if the ROM puts the flash in 8D-8D-8D mode for better boot speed, we
would have the same problem.

--
Regards,
Pratyush Yadav
Texas Instruments India

2020-02-28 13:20:04

by Boris Brezillon

[permalink] [raw]
Subject: Re: [PATCH v2 06/11] mtd: spi-nor: add support for DTR protocol

On Fri, 28 Feb 2020 17:37:50 +0530
Pratyush Yadav <[email protected]> wrote:


> > Isn't there an EXIT sequence that allows NORs to return to a single
> > SPI state?
>
> Yes there is, but it comes with a lot of strings attached. There is a
> hardware reset pin on some flashes that puts the flash in Power-on-Reset
> (POR) mode. But that pin is not mandatory. It also might not be
> connected on a given board.
>
> The other option is a "Soft Reset" (also optional), which puts the flash
> in POR mode after it is given the soft reset command. But to send the
> command you need to know the mode the device is in. On top of that, the
> Soft Reset opcode differs between flashes. According to the xSPI spec,
> some flashes can have the opcode as 0xF0 and some others can have it as
> a two command sequence of 0x66 and 0x99.
>
> And the cherry on top is the fact that these reset operations return to
> a state based on the value of the non-volatile bits. So, if the
> non-volatile configuration is 8D-8D-8D mode, then all these resets
> achieve nothing.

Looks like flash vendors don't learn from their mistakes, they keep
adding more features without really thinking about backward
compatibility :-(.

> >
> > >
> > > So, the only solution I can now think of is having the flash name in
> > > its compatible string in the device tree. This way we can skip all
> > > the Read ID ugliness and can have flash-specific hooks to make it
> > > easier to detect the mode it is in (though I wonder if it is even
> > > possible to detect the mode in a flash that doesn't have SFDP in
> > > 8D-8D-8D).
> >
> > Hm, I'd really like to avoid that if possible.
>
> Unfortunately, I don't really see a better alternative. Just so I
> understand this better, why do you think it is something worth avoiding?

There are 2 main reasons:

1/ board manufacturers usually source their flashes from different
vendors so they're not tied to one of them. That means you can't really
make the compatible too specific or you'd have to deal with DT variants
(one variant per-flash).

2/ I feel like once we start accepting specific compats, people will
try to abuse it and decide that they need one for their flash too,
before even trying to see if there's not a different way to detect the
flash.

2020-03-02 09:54:33

by Pratyush Yadav

[permalink] [raw]
Subject: Re: [PATCH v2 01/11] dt-bindings: spi: allow expressing DTR capability

On 27/02/20 06:03PM, Geert Uytterhoeven wrote:
> Hi Mark,
>
> On Thu, Feb 27, 2020 at 5:44 PM Mark Brown <[email protected]> wrote:
> > On Thu, Feb 27, 2020 at 05:40:31PM +0100, Geert Uytterhoeven wrote:
> > > On Thu, Feb 27, 2020 at 5:28 PM Mark Brown <[email protected]> wrote:
> > > > It's what we do for other properties, and if this is anything like the
> > > > other things adding extra wiring you can't assume that the ability to
> > > > use the feature for TX implies RX.
> >
> > > Double Transfer Rate uses the same wire.
> >
> > But is it still on either the TX or RX signals?
>
> E.g. good old Spansion S25FL512S supports single/dual/quad DDR, but
> apparently only for read, not write.
> Other FLASHes may support both directions. I guess.

The flash datasheet says under section 9.4 (Read Memory Array Commands):

Some commands transfer address and data on both the rising edge and
falling edge of SCK. These are called Double Data Rate (DDR) commands.

Since the address is transferred in DDR mode, both Tx and Rx signals use
DDR.

So, unless we have a flash that supports a mode like 1S-1S-8D, we don't
really need two properties.

--
Regards,
Pratyush Yadav
Texas Instruments India