From: Boris Brezillon <boris.brezil...@bootlin.com>

The spi-mem layer provides a spi_mem_supports_op() function to check
whether a specific operation is supported by the controller or not.
This is much more accurate than the hwcaps selection logic based on
SPI_{RX,TX}_ flags.

Rework the hwcaps selection logic to use spi_mem_supports_op() when
nor->spimem != NULL.

Signed-off-by: Boris Brezillon <boris.brezil...@bootlin.com>
Signed-off-by: Vignesh Raghavendra <vigne...@ti.com>
---
v5:
Fix a bug in spi_nor_spimem_check_op()

 drivers/mtd/spi-nor/spi-nor.c | 184 +++++++++++++++++++++++++++-------
 include/linux/mtd/spi-nor.h   |  14 +++
 2 files changed, 162 insertions(+), 36 deletions(-)

diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index de906ab907c7..eb258ce5a6dd 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -2951,6 +2951,130 @@ static int spi_nor_read_sfdp(struct spi_nor *nor, u32 
addr,
        return ret;
 }
 
+/**
+ * spi_nor_spimem_check_op - check if the operation is supported
+ *                           by controller
+ *@nor:        pointer to a 'struct spi_nor'
+ *@op:         pointer to op template to be checked
+ *
+ * Returns 0 if operation is supported, -ENOTSUPP otherwise.
+ */
+static int spi_nor_spimem_check_op(struct spi_nor *nor,
+                                  struct spi_mem_op *op)
+{
+       /*
+        * First test with 4 address bytes. The opcode itself might
+        * be a 3B addressing opcode but we don't care, because
+        * SPI controller implementation should not check the opcode,
+        * but just the sequence.
+        */
+       op->addr.nbytes = 4;
+       if (!spi_mem_supports_op(nor->spimem, op)) {
+               /* If flash size <16MB, 3 address bytes are sufficient */
+               if (nor->mtd.size <= SZ_16M) {
+                       op->addr.nbytes = 3;
+                       if (!spi_mem_supports_op(nor->spimem, op))
+                               return -ENOTSUPP;
+                       return 0;
+               }
+               return -ENOTSUPP;
+       }
+
+       return 0;
+}
+
+/**
+ * spi_nor_spimem_check_readop - check if the read op is supported
+ *                               by controller
+ *@nor:         pointer to a 'struct spi_nor'
+ *@read:        pointer to op template to be checked
+ *
+ * Returns 0 if operation is supported, -ENOTSUPP otherwise.
+ */
+static int spi_nor_spimem_check_readop(struct spi_nor *nor,
+                                      const struct spi_nor_read_command *read)
+{
+       struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(read->opcode, 1),
+                                         SPI_MEM_OP_ADDR(3, 0, 1),
+                                         SPI_MEM_OP_DUMMY(0, 1),
+                                         SPI_MEM_OP_DATA_IN(0, NULL, 1));
+
+       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;
+
+       return spi_nor_spimem_check_op(nor, &op);
+}
+
+/**
+ * spi_nor_spimem_check_pp - check if the page program op is supported
+ *                           by controller
+ *@nor:         pointer to a 'struct spi_nor'
+ *@pp:          pointer to op template to be checked
+ *
+ * Returns 0 if operation is supported, -ENOTSUPP otherwise.
+ */
+static int spi_nor_spimem_check_pp(struct spi_nor *nor,
+                                  const struct spi_nor_pp_command *pp)
+{
+       struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(pp->opcode, 1),
+                                         SPI_MEM_OP_ADDR(3, 0, 1),
+                                         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);
+
+       return spi_nor_spimem_check_op(nor, &op);
+}
+
+/**
+ * spi_nor_spimem_adjust_hwcaps - Find optimal Read/Write protocol
+ *                                based on SPI controller capabilities
+ * @nor:        pointer to a 'struct spi_nor'
+ * @params:     pointer to the 'struct spi_nor_flash_parameter'
+ *              representing SPI NOR flash capabilities
+ * @hwcaps:     pointer to resulting capabilities after adjusting
+ *              according to controller and flash's capability
+ */
+static void
+spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor,
+                            const struct spi_nor_flash_parameter *params,
+                            u32 *hwcaps)
+{
+       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;
+
+       for (cap = 0; cap < sizeof(*hwcaps) * BITS_PER_BYTE; cap++) {
+               int rdidx, ppidx;
+
+               if (!(*hwcaps & BIT(cap)))
+                       continue;
+
+               rdidx = spi_nor_hwcaps_read2cmd(BIT(cap));
+               if (rdidx >= 0 &&
+                   spi_nor_spimem_check_readop(nor, &params->reads[rdidx]))
+                       *hwcaps &= ~BIT(cap);
+
+               ppidx = spi_nor_hwcaps_pp2cmd(BIT(cap));
+               if (ppidx < 0)
+                       continue;
+
+               if (spi_nor_spimem_check_pp(nor,
+                                           &params->page_programs[ppidx]))
+                       *hwcaps &= ~BIT(cap);
+       }
+}
+
 /**
  * spi_nor_read_sfdp_dma_unsafe() - read Serial Flash Discoverable Parameters.
  * @nor:       pointer to a 'struct spi_nor'
@@ -4360,16 +4484,25 @@ static int spi_nor_setup(struct spi_nor *nor,
         */
        shared_mask = hwcaps->mask & params->hwcaps.mask;
 
-       /* SPI n-n-n protocols are not supported yet. */
-       ignored_mask = (SNOR_HWCAPS_READ_2_2_2 |
-                       SNOR_HWCAPS_READ_4_4_4 |
-                       SNOR_HWCAPS_READ_8_8_8 |
-                       SNOR_HWCAPS_PP_4_4_4 |
-                       SNOR_HWCAPS_PP_8_8_8);
-       if (shared_mask & ignored_mask) {
-               dev_dbg(nor->dev,
-                       "SPI n-n-n protocols are not supported yet.\n");
-               shared_mask &= ~ignored_mask;
+       if (nor->spimem) {
+               /*
+                * When called from spi_nor_probe(), all caps are set and we
+                * need to discard some of them based on what the SPI
+                * controller actually supports (using spi_mem_supports_op()).
+                */
+               spi_nor_spimem_adjust_hwcaps(nor, params, &shared_mask);
+       } else {
+               /*
+                * SPI n-n-n protocols are not supported when the SPI
+                * controller directly implements the spi_nor interface.
+                * Yet another reason to switch to spi-mem.
+                */
+               ignored_mask = SNOR_HWCAPS_X_X_X;
+               if (shared_mask & ignored_mask) {
+                       dev_dbg(nor->dev,
+                               "SPI n-n-n protocols are not supported.\n");
+                       shared_mask &= ~ignored_mask;
+               }
        }
 
        /* Select the (Fast) Read command. */
@@ -4712,11 +4845,11 @@ static int spi_nor_probe(struct spi_mem *spimem)
        struct spi_device *spi = spimem->spi;
        struct flash_platform_data *data = dev_get_platdata(&spi->dev);
        struct spi_nor *nor;
-       struct spi_nor_hwcaps hwcaps = {
-               .mask = SNOR_HWCAPS_READ |
-                       SNOR_HWCAPS_READ_FAST |
-                       SNOR_HWCAPS_PP,
-       };
+       /*
+        * Enable all caps by default. The core will mask them after
+        * checking what's really supported using spi_mem_supports_op().
+        */
+       const struct spi_nor_hwcaps hwcaps = { .mask = SNOR_HWCAPS_ALL };
        char *flash_name;
        int ret;
 
@@ -4730,27 +4863,6 @@ static int spi_nor_probe(struct spi_mem *spimem)
 
        spi_mem_set_drvdata(spimem, nor);
 
-       if (spi->mode & SPI_RX_OCTAL) {
-               hwcaps.mask |= SNOR_HWCAPS_READ_1_1_8;
-
-               if (spi->mode & SPI_TX_OCTAL)
-                       hwcaps.mask |= (SNOR_HWCAPS_READ_1_8_8 |
-                                       SNOR_HWCAPS_PP_1_1_8 |
-                                       SNOR_HWCAPS_PP_1_8_8);
-       } else if (spi->mode & SPI_RX_QUAD) {
-               hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
-
-               if (spi->mode & SPI_TX_QUAD)
-                       hwcaps.mask |= (SNOR_HWCAPS_READ_1_4_4 |
-                                       SNOR_HWCAPS_PP_1_1_4 |
-                                       SNOR_HWCAPS_PP_1_4_4);
-       } else if (spi->mode & SPI_RX_DUAL) {
-               hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
-
-               if (spi->mode & SPI_TX_DUAL)
-                       hwcaps.mask |= SNOR_HWCAPS_READ_1_2_2;
-       }
-
        if (data && data->name)
                nor->mtd.name = data->name;
 
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 4f35b1877889..5f1acb1867dd 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -524,6 +524,20 @@ struct spi_nor_hwcaps {
 #define SNOR_HWCAPS_PP_1_8_8   BIT(21)
 #define SNOR_HWCAPS_PP_8_8_8   BIT(22)
 
+#define SNOR_HWCAPS_X_X_X      (SNOR_HWCAPS_READ_2_2_2 |       \
+                                SNOR_HWCAPS_READ_4_4_4 |       \
+                                SNOR_HWCAPS_READ_8_8_8 |       \
+                                SNOR_HWCAPS_PP_4_4_4 |         \
+                                SNOR_HWCAPS_PP_8_8_8)
+
+#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)
+
+#define SNOR_HWCAPS_ALL                (SNOR_HWCAPS_READ_MASK |        \
+                                SNOR_HWCAPS_PP_MASK)
+
 /**
  * spi_nor_scan() - scan the SPI NOR
  * @nor:       the spi_nor structure
-- 
2.22.0

Reply via email to