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().

To make sure the build doesn't break for boards not using CONFIG_DM_SPI,
add a simple SPI_{RX,TX}_ based hwcaps selection logic in spi-mem-nodm
similar to spi_mem_default_supports_op(). This change is only
compile-tested.

To avoid SPL size problems on the x530 board, the old hwcaps selection
is still kept around. Leaving the code in-place was getting difficult to
read and understand, so the code is restructured to have it all in one
isolated function. As a result of this, the parameter hwcaps to
spi_nor_setup() is no longer needed. Remove it.

Based on the Linux commit c76f5089796a (mtd: spi-nor: Rework hwcaps
selection for the spi-mem case, 2019-08-06)

Signed-off-by: Pratyush Yadav <p.ya...@ti.com>
---
 drivers/mtd/spi/Kconfig        |   9 ++
 drivers/mtd/spi/spi-nor-core.c | 244 ++++++++++++++++++++++++++-------
 drivers/spi/spi-mem-nodm.c     |  62 +++++++++
 include/linux/mtd/spi-nor.h    |  17 ++-
 4 files changed, 280 insertions(+), 52 deletions(-)

diff --git a/drivers/mtd/spi/Kconfig b/drivers/mtd/spi/Kconfig
index 018e8c597e..09ec18cdf2 100644
--- a/drivers/mtd/spi/Kconfig
+++ b/drivers/mtd/spi/Kconfig
@@ -88,6 +88,15 @@ config SPI_FLASH_SFDP_SUPPORT
         SPI NOR flashes using Serial Flash Discoverable Parameters (SFDP)
         tables as per JESD216 standard.
 
+config SPI_FLASH_SMART_HWCAPS
+       bool "Smart hardware capability detection based on SPI MEM 
supports_op() hook"
+       default y
+       help
+        Enable support for smart hardware capability detection based on SPI
+        MEM supports_op() hook that lets controllers express whether they
+        can support a type of operation in a much more refined way compared
+        to using flags like SPI_RX_DUAL, SPI_TX_QUAD, etc.
+
 config SPI_FLASH_BAR
        bool "SPI flash Bank/Extended address register support"
        help
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c
index 5264d24fd0..7b440d4477 100644
--- a/drivers/mtd/spi/spi-nor-core.c
+++ b/drivers/mtd/spi/spi-nor-core.c
@@ -2295,6 +2295,194 @@ static int spi_nor_hwcaps_pp2cmd(u32 hwcaps)
                                  ARRAY_SIZE(hwcaps_pp2cmd));
 }
 
+#ifdef CONFIG_SPI_FLASH_SMART_HWCAPS
+/**
+ * spi_nor_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_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->spi, op)) {
+               if (nor->mtd.size > SZ_16M)
+                       return -ENOTSUPP;
+
+               /* If flash size <= 16MB, 3 address bytes are sufficient */
+               op->addr.nbytes = 3;
+               if (!spi_mem_supports_op(nor->spi, op))
+                       return -ENOTSUPP;
+       }
+
+       return 0;
+}
+
+/**
+ * spi_nor_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_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_check_op(nor, &op);
+}
+
+/**
+ * spi_nor_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_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_check_op(nor, &op);
+}
+
+/**
+ * spi_nor_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
+ *
+ * Discard caps based on what the SPI controller actually supports (using
+ * spi_mem_supports_op()).
+ */
+static void
+spi_nor_adjust_hwcaps(struct spi_nor *nor,
+                     const struct spi_nor_flash_parameter *params,
+                     u32 *hwcaps)
+{
+       unsigned int cap;
+
+       /*
+        * Enable all caps by default. We will mask them after checking what's
+        * really supported using spi_mem_supports_op().
+        */
+       *hwcaps = SNOR_HWCAPS_ALL;
+
+       /* 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_check_readop(nor, &params->reads[rdidx]))
+                       *hwcaps &= ~BIT(cap);
+
+               ppidx = spi_nor_hwcaps_pp2cmd(BIT(cap));
+               if (ppidx < 0)
+                       continue;
+
+               if (spi_nor_check_pp(nor, &params->page_programs[ppidx]))
+                       *hwcaps &= ~BIT(cap);
+       }
+}
+#else
+/**
+ * spi_nor_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
+ *
+ * Select caps based on what the SPI controller and SPI flash both support.
+ */
+static void
+spi_nor_adjust_hwcaps(struct spi_nor *nor,
+                     const struct spi_nor_flash_parameter *params,
+                     u32 *hwcaps)
+{
+       struct spi_slave *spi = nor->spi;
+       u32 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);
+       u32 spi_hwcaps = (SNOR_HWCAPS_READ | SNOR_HWCAPS_READ_FAST |
+                         SNOR_HWCAPS_PP);
+
+       /* Get the hardware capabilities the SPI controller supports. */
+       if (spi->mode & SPI_RX_OCTAL) {
+               spi_hwcaps |= SNOR_HWCAPS_READ_1_1_8;
+
+               if (spi->mode & SPI_TX_OCTAL)
+                       spi_hwcaps |= (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) {
+               spi_hwcaps |= SNOR_HWCAPS_READ_1_1_4;
+
+               if (spi->mode & SPI_TX_QUAD)
+                       spi_hwcaps |= (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) {
+               spi_hwcaps |= SNOR_HWCAPS_READ_1_1_2;
+
+               if (spi->mode & SPI_TX_DUAL)
+                       spi_hwcaps |= SNOR_HWCAPS_READ_1_2_2;
+       }
+
+       /*
+        * Keep only the hardware capabilities supported by both the SPI
+        * controller and the SPI flash memory.
+        */
+       *hwcaps = spi_hwcaps & params->hwcaps.mask;
+       if (*hwcaps & ignored_mask) {
+               dev_dbg(nor->dev,
+                       "SPI n-n-n protocols are not supported yet.\n");
+               *hwcaps &= ~ignored_mask;
+       }
+}
+#endif /* CONFIG_SPI_FLASH_SMART_HWCAPS */
+
 static int spi_nor_select_read(struct spi_nor *nor,
                               const struct spi_nor_flash_parameter *params,
                               u32 shared_hwcaps)
@@ -2375,30 +2563,13 @@ static int spi_nor_select_erase(struct spi_nor *nor,
 
 static int spi_nor_default_setup(struct spi_nor *nor,
                                 const struct flash_info *info,
-                                const struct spi_nor_flash_parameter *params,
-                                const struct spi_nor_hwcaps *hwcaps)
+                                const struct spi_nor_flash_parameter *params)
 {
-       u32 ignored_mask, shared_mask;
+       u32 shared_mask;
        bool enable_quad_io;
        int err;
 
-       /*
-        * Keep only the hardware capabilities supported by both the SPI
-        * controller and the SPI flash memory.
-        */
-       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;
-       }
+       spi_nor_adjust_hwcaps(nor, params, &shared_mask);
 
        /* Select the (Fast) Read command. */
        err = spi_nor_select_read(nor, params, shared_mask);
@@ -2436,13 +2607,12 @@ static int spi_nor_default_setup(struct spi_nor *nor,
 }
 
 static int spi_nor_setup(struct spi_nor *nor, const struct flash_info *info,
-                        const struct spi_nor_flash_parameter *params,
-                        const struct spi_nor_hwcaps *hwcaps)
+                        const struct spi_nor_flash_parameter *params)
 {
        if (!nor->setup)
                return 0;
 
-       return nor->setup(nor, info, params, hwcaps);
+       return nor->setup(nor, info, params);
 }
 
 static int spi_nor_init(struct spi_nor *nor)
@@ -2497,11 +2667,6 @@ int spi_nor_scan(struct spi_nor *nor)
        struct spi_nor_flash_parameter params;
        const struct flash_info *info = NULL;
        struct mtd_info *mtd = &nor->mtd;
-       struct spi_nor_hwcaps hwcaps = {
-               .mask = SNOR_HWCAPS_READ |
-                       SNOR_HWCAPS_READ_FAST |
-                       SNOR_HWCAPS_PP,
-       };
        struct spi_slave *spi = nor->spi;
        int ret;
 
@@ -2516,27 +2681,6 @@ int spi_nor_scan(struct spi_nor *nor)
 
        nor->setup = spi_nor_default_setup;
 
-       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;
-       }
-
        info = spi_nor_read_id(nor);
        if (IS_ERR_OR_NULL(info))
                return -ENOENT;
@@ -2610,7 +2754,7 @@ int spi_nor_scan(struct spi_nor *nor)
         * - set the SPI protocols for register and memory accesses.
         * - set the Quad Enable bit if needed (required by SPI x-y-4 protos).
         */
-       ret = spi_nor_setup(nor, info, &params, &hwcaps);
+       ret = spi_nor_setup(nor, info, &params);
        if (ret)
                return ret;
 
diff --git a/drivers/spi/spi-mem-nodm.c b/drivers/spi/spi-mem-nodm.c
index db54101383..050d25040c 100644
--- a/drivers/spi/spi-mem-nodm.c
+++ b/drivers/spi/spi-mem-nodm.c
@@ -105,3 +105,65 @@ int spi_mem_adjust_op_size(struct spi_slave *slave,
 
        return 0;
 }
+
+static int spi_check_buswidth_req(struct spi_slave *slave, u8 buswidth, bool 
tx)
+{
+       u32 mode = slave->mode;
+
+       switch (buswidth) {
+       case 1:
+               return 0;
+
+       case 2:
+               if ((tx && (mode & (SPI_TX_DUAL | SPI_TX_QUAD))) ||
+                   (!tx && (mode & (SPI_RX_DUAL | SPI_RX_QUAD))))
+                       return 0;
+
+               break;
+
+       case 4:
+               if ((tx && (mode & SPI_TX_QUAD)) ||
+                   (!tx && (mode & SPI_RX_QUAD)))
+                       return 0;
+
+               break;
+       case 8:
+               if ((tx && (mode & SPI_TX_OCTAL)) ||
+                   (!tx && (mode & SPI_RX_OCTAL)))
+                       return 0;
+
+               break;
+
+       default:
+               break;
+       }
+
+       return -ENOTSUPP;
+}
+
+bool spi_mem_supports_op(struct spi_slave *slave, const struct spi_mem_op *op)
+{
+       if (spi_check_buswidth_req(slave, op->cmd.buswidth, true))
+               return false;
+
+       if (op->addr.nbytes &&
+           spi_check_buswidth_req(slave, op->addr.buswidth, true))
+               return false;
+
+       if (op->dummy.nbytes &&
+           spi_check_buswidth_req(slave, op->dummy.buswidth, true))
+               return false;
+
+       if (op->data.dir != SPI_MEM_NO_DATA &&
+           spi_check_buswidth_req(slave, op->data.buswidth,
+                                  op->data.dir == SPI_MEM_DATA_OUT))
+               return false;
+
+       if (op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr)
+               return false;
+
+       if (op->cmd.nbytes != 1)
+               return false;
+
+       return true;
+}
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 5842e9d6ee..cca7919fdd 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -312,6 +312,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)
+
 struct spi_nor_read_command {
        u8                      num_mode_clocks;
        u8                      num_wait_states;
@@ -459,8 +473,7 @@ struct spi_nor {
        struct spi_nor_fixups   *fixups;
 
        int (*setup)(struct spi_nor *nor, const struct flash_info *info,
-                    const struct spi_nor_flash_parameter *params,
-                    const struct spi_nor_hwcaps *hwcaps);
+                    const struct spi_nor_flash_parameter *params);
        int (*prepare)(struct spi_nor *nor, enum spi_nor_ops ops);
        void (*unprepare)(struct spi_nor *nor, enum spi_nor_ops ops);
        int (*read_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len);
-- 
2.25.0

Reply via email to