linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH 00/10] SPINAND PHY Tuning Series
@ 2025-08-11 19:32 Santhosh Kumar K
  2025-08-11 19:32 ` [RFC PATCH 01/10] spi: spi-mem: Introduce support for tuning controller Santhosh Kumar K
                   ` (9 more replies)
  0 siblings, 10 replies; 16+ messages in thread
From: Santhosh Kumar K @ 2025-08-11 19:32 UTC (permalink / raw)
  To: miquel.raynal, richard, vigneshr, broonie, tudor.ambarus,
	pratyush, mwalle, p-mantena
  Cc: linux-spi, linux-mtd, linux-kernel, s-k6, a-dutta, u-kumar1,
	praneeth

High-speed operation of certain SPI NAND/NOR controllers, such as the
Cadence OSPI controller, requires execution of a PHY tuning sequence
after switching the device to the appropriate mode (SDR or DDR).
Without PHY, the controller in SDR mode can operate only at one-fourth
of the reference clock. This series introduces an SDR-based PHY tuning
procedure to enable higher-speed operation in SDR mode.

A new _execute_tuning() hook is added to the mtd_info structure for
each MTD device. This hook is invoked when a flash partition named
'ospi.phypattern' is detected. The flash-specific _execute_tuning()
implementation retrieves the PHY tuning parameters-such as the
pre-defined tuning pattern and its size-and uses them to perform a
readback/compare operation against the known pre-flashed data in
'ospi.phypattern' as part of the tuning process.

From the flash driver, spi_mem_execute_tuning() is called with an
appropriate read_op. This function invokes cqspi_execute_tuning(),
which contains the PHY tuning algorithm. These APIs are flash-agnostic,
relying only on the correct read_op being passed. DDR mode tuning with
DQS support will be implemented in a future series.

In the NOR driver, _execute_tuning() follows the same approach of
pre-flashing a known pattern into the target partition and reading it
back during tuning. For NAND flashes, the tuning pattern can be written
directly to the cache via write_to_cache() and subsequently read back
using read_from_cache() during the tuning procedure. This support will
be added in a later phase.

At present, the series does not implement fallback handling for tuning
failures (e.g., reducing maximum frequency or adjusting dummy cycles).
Future enhancements will add fallback mechanisms to select a lower
operating frequency and optimal dummy cycle configuration.

Device Tree updates required for this functionality will be submitted
in a separate patch series after this one is merged.

This series was tested on TI's AM62A SK with W35N01JW OSPI NAND flash.
The read and write throughput with and without PHY:

Without PHY:
     READ: 7.1 MB/s
     WRITE: 6 MB/s
     
Wth PHY:
     READ: 35.1 MB/s
     WRITE: 9.2 MB/s
     
Repo: https://github.com/santhosh21/linux/commits/spinand_phy_rfc_v1
Test log: https://gist.github.com/santhosh21/d2d52cc03d2423200971b0ab9c3b9365

Signed-off-by: Santhosh Kumar K <s-k6@ti.com>

Pratyush Yadav (2):
  spi: spi-mem: Introduce support for tuning controller
  spi: cadence-quadspi: Enable PHY for aligned DAC reads

Santhosh Kumar K (8):
  spi: spi-mem: Define spi_mem_tuning_params and
    spi_mem_get_tuning_params()
  mtd: nand: spi: Introduce _execute_tuning for mtd devices
  mtd: mtdcore: Call mtd_execute_tuning during mtd_register
  spi: cadence-quadspi: Move cqspi_readdata_capture() above all
    operations
  spi: cadence-quadspi: Use BIT() macro for CQSPI_REG_READCAPTURE_BYPASS
  spi: cadence-quadspi: Enable PHY for data writes
  spi: cadence-quadspi: Implement PHY for higher frequencies in SDR mode
  spi: cadence-quadspi: Define cqspi_get_tuning_params()

 drivers/mtd/mtdcore.c             |  19 +
 drivers/mtd/mtdcore.h             |   1 +
 drivers/mtd/nand/spi/core.c       |  61 +++
 drivers/spi/spi-cadence-quadspi.c | 727 ++++++++++++++++++++++++++++--
 drivers/spi/spi-mem.c             |  22 +
 include/linux/mtd/mtd.h           |   1 +
 include/linux/spi/spi-mem.h       |  23 +
 7 files changed, 823 insertions(+), 31 deletions(-)

-- 
2.34.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [RFC PATCH 01/10] spi: spi-mem: Introduce support for tuning controller
  2025-08-11 19:32 [RFC PATCH 00/10] SPINAND PHY Tuning Series Santhosh Kumar K
@ 2025-08-11 19:32 ` Santhosh Kumar K
  2025-08-13 20:26   ` Mark Brown
  2025-08-11 19:32 ` [RFC PATCH 02/10] spi: spi-mem: Define spi_mem_tuning_params and spi_mem_get_tuning_params() Santhosh Kumar K
                   ` (8 subsequent siblings)
  9 siblings, 1 reply; 16+ messages in thread
From: Santhosh Kumar K @ 2025-08-11 19:32 UTC (permalink / raw)
  To: miquel.raynal, richard, vigneshr, broonie, tudor.ambarus,
	pratyush, mwalle, p-mantena
  Cc: linux-spi, linux-mtd, linux-kernel, s-k6, a-dutta, u-kumar1,
	praneeth

From: Pratyush Yadav <pratyush@kernel.org>

Some controllers like the Cadence OSPI controller need to perform a
tuning sequence to operate at high data rates. Tuning is needs to happen
once the device is switched to appropriate mode (say 8S-8S-8S or
8D-8D-8D). Add a hook that spi-mem client devices can call in order to tune
the controller to operate in a given mode and data rate.

This is somewhat similar to eMMC/SD tuning for higher speed modes like
HS200, but there isn't a standard specification around the same though.

Signed-off-by: Pratyush Yadav <pratyush@kernel.org>
Signed-off-by: Santhosh Kumar K <s-k6@ti.com>
---
 drivers/spi/spi-mem.c       | 11 +++++++++++
 include/linux/spi/spi-mem.h |  9 +++++++++
 2 files changed, 20 insertions(+)

diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c
index 064b99204d9a..6c254291ee23 100644
--- a/drivers/spi/spi-mem.c
+++ b/drivers/spi/spi-mem.c
@@ -559,6 +559,17 @@ 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_execute_tuning(struct spi_mem *mem, const struct spi_mem_op *op)
+{
+	struct spi_controller *ctlr = mem->spi->controller;
+
+	if (!ctlr->mem_ops || !ctlr->mem_ops->execute_tuning)
+		return -EOPNOTSUPP;
+
+	return ctlr->mem_ops->execute_tuning(mem, op);
+}
+EXPORT_SYMBOL_GPL(spi_mem_execute_tuning);
+
 /**
  * spi_mem_adjust_op_freq() - Adjust the frequency of a SPI mem operation to
  *			      match controller, PCB and chip limitations
diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h
index 82390712794c..639fee61251c 100644
--- a/include/linux/spi/spi-mem.h
+++ b/include/linux/spi/spi-mem.h
@@ -314,6 +314,12 @@ static inline void *spi_mem_get_drvdata(struct spi_mem *mem)
  * @poll_status: poll memory device status until (status & mask) == match or
  *               when the timeout has expired. It fills the data buffer with
  *               the last status value.
+ * @execute_tuning: perform PHY tuning to achieve high speed SPI operations.
+ *		    Should be called after the SPI memory device has been
+ *		    completely initialized. The op passed should contain a
+ *		    template for the read operation used for the device so the
+ *		    controller can decide what type of tuning is required
+ *		    for the type of read op passed.
  *
  * This interface should be implemented by SPI controllers providing an
  * high-level interface to execute SPI memory operation, which is usually the
@@ -344,6 +350,8 @@ struct spi_controller_mem_ops {
 			   unsigned long initial_delay_us,
 			   unsigned long polling_rate_us,
 			   unsigned long timeout_ms);
+	int (*execute_tuning)(struct spi_mem *mem,
+			      const struct spi_mem_op *op);
 };
 
 /**
@@ -423,6 +431,7 @@ bool spi_mem_default_supports_op(struct spi_mem *mem,
 #endif /* CONFIG_SPI_MEM */
 
 int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op);
+int spi_mem_execute_tuning(struct spi_mem *mem, const struct spi_mem_op *op);
 void spi_mem_adjust_op_freq(struct spi_mem *mem, struct spi_mem_op *op);
 u64 spi_mem_calc_op_duration(struct spi_mem *mem, struct spi_mem_op *op);
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [RFC PATCH 02/10] spi: spi-mem: Define spi_mem_tuning_params and spi_mem_get_tuning_params()
  2025-08-11 19:32 [RFC PATCH 00/10] SPINAND PHY Tuning Series Santhosh Kumar K
  2025-08-11 19:32 ` [RFC PATCH 01/10] spi: spi-mem: Introduce support for tuning controller Santhosh Kumar K
@ 2025-08-11 19:32 ` Santhosh Kumar K
  2025-08-11 19:32 ` [RFC PATCH 03/10] mtd: nand: spi: Introduce _execute_tuning for mtd devices Santhosh Kumar K
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 16+ messages in thread
From: Santhosh Kumar K @ 2025-08-11 19:32 UTC (permalink / raw)
  To: miquel.raynal, richard, vigneshr, broonie, tudor.ambarus,
	pratyush, mwalle, p-mantena
  Cc: linux-spi, linux-mtd, linux-kernel, s-k6, a-dutta, u-kumar1,
	praneeth

Define a structure 'spi_mem_tuning_params' to store the PHY tuning
pattern and size of the pattern. Also, define a function
'spi_mem_get_tuning_params' to retrieve the information from SPI
controller.
This is required as different SPI controller may have different
stress or attack pattern of different length that needs to be read in
order to tune the controller correctly. In absence of such callback,
spi-mem clients can use static tables on the flashes like SFDP or NAND
param page values.

Signed-off-by: Santhosh Kumar K <s-k6@ti.com>
---
 drivers/spi/spi-mem.c       | 11 +++++++++++
 include/linux/spi/spi-mem.h | 14 ++++++++++++++
 2 files changed, 25 insertions(+)

diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c
index 6c254291ee23..cfee5288fc81 100644
--- a/drivers/spi/spi-mem.c
+++ b/drivers/spi/spi-mem.c
@@ -570,6 +570,17 @@ int spi_mem_execute_tuning(struct spi_mem *mem, const struct spi_mem_op *op)
 }
 EXPORT_SYMBOL_GPL(spi_mem_execute_tuning);
 
+int spi_mem_get_tuning_params(struct spi_mem *mem, struct spi_mem_tuning_params *tuning_params)
+{
+	struct spi_controller *ctlr = mem->spi->controller;
+
+	if (!ctlr->mem_ops || !ctlr->mem_ops->get_tuning_params)
+		return -EOPNOTSUPP;
+
+	return ctlr->mem_ops->get_tuning_params(mem, tuning_params);
+}
+EXPORT_SYMBOL_GPL(spi_mem_get_tuning_params);
+
 /**
  * spi_mem_adjust_op_freq() - Adjust the frequency of a SPI mem operation to
  *			      match controller, PCB and chip limitations
diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h
index 639fee61251c..f0e96cd4a6d4 100644
--- a/include/linux/spi/spi-mem.h
+++ b/include/linux/spi/spi-mem.h
@@ -238,6 +238,16 @@ struct spi_mem_dirmap_desc {
 	void *priv;
 };
 
+/**
+ * struct spi_mem_tuning_params - describes the Tuning parameters
+ * @pattern_ptr: pointer to the tuning pattern
+ * @pattern_size: size of the tuning pattern
+ */
+struct spi_mem_tuning_params {
+	u8 *pattern_ptr;
+	unsigned int pattern_size;
+};
+
 /**
  * struct spi_mem - describes a SPI memory device
  * @spi: the underlying SPI device
@@ -352,6 +362,8 @@ struct spi_controller_mem_ops {
 			   unsigned long timeout_ms);
 	int (*execute_tuning)(struct spi_mem *mem,
 			      const struct spi_mem_op *op);
+	int (*get_tuning_params)(struct spi_mem *mem,
+				 struct spi_mem_tuning_params *tuning_params);
 };
 
 /**
@@ -434,6 +446,8 @@ int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op);
 int spi_mem_execute_tuning(struct spi_mem *mem, const struct spi_mem_op *op);
 void spi_mem_adjust_op_freq(struct spi_mem *mem, struct spi_mem_op *op);
 u64 spi_mem_calc_op_duration(struct spi_mem *mem, struct spi_mem_op *op);
+int spi_mem_get_tuning_params(struct spi_mem *mem,
+			      struct spi_mem_tuning_params *tuning_params);
 
 bool spi_mem_supports_op(struct spi_mem *mem,
 			 const struct spi_mem_op *op);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [RFC PATCH 03/10] mtd: nand: spi: Introduce _execute_tuning for mtd devices
  2025-08-11 19:32 [RFC PATCH 00/10] SPINAND PHY Tuning Series Santhosh Kumar K
  2025-08-11 19:32 ` [RFC PATCH 01/10] spi: spi-mem: Introduce support for tuning controller Santhosh Kumar K
  2025-08-11 19:32 ` [RFC PATCH 02/10] spi: spi-mem: Define spi_mem_tuning_params and spi_mem_get_tuning_params() Santhosh Kumar K
@ 2025-08-11 19:32 ` Santhosh Kumar K
  2025-08-11 19:32 ` [RFC PATCH 04/10] mtd: mtdcore: Call mtd_execute_tuning during mtd_register Santhosh Kumar K
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 16+ messages in thread
From: Santhosh Kumar K @ 2025-08-11 19:32 UTC (permalink / raw)
  To: miquel.raynal, richard, vigneshr, broonie, tudor.ambarus,
	pratyush, mwalle, p-mantena
  Cc: linux-spi, linux-mtd, linux-kernel, s-k6, a-dutta, u-kumar1,
	praneeth

Add _execute_tuning to mtd_info allowing mtd devices to run their own
PHY tuning procedure to run at higher frequencies.

Signed-off-by: Santhosh Kumar K <s-k6@ti.com>
---
 drivers/mtd/nand/spi/core.c | 61 +++++++++++++++++++++++++++++++++++++
 include/linux/mtd/mtd.h     |  1 +
 2 files changed, 62 insertions(+)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index b0898990b2a5..c890a42cdb0a 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -1483,6 +1483,66 @@ static void spinand_mtd_resume(struct mtd_info *mtd)
 	spinand_ecc_enable(spinand, false);
 }
 
+static int spinand_mtd_execute_tuning(struct mtd_info *mtd, struct mtd_part *part)
+{
+	struct spinand_device *spinand = mtd_to_spinand(mtd);
+	struct nand_device *nand = spinand_to_nand(spinand);
+	struct nand_pos page_pos;
+	struct nand_page_io_req page_req;
+	struct spi_mem_op read_page_op;
+	struct spi_mem_tuning_params *tuning_params;
+	int ret, pageoffs;
+	u8 status;
+
+	tuning_params = kzalloc(sizeof(*tuning_params), GFP_KERNEL);
+	if (!tuning_params)
+		return -ENOMEM;
+
+	ret = spi_mem_get_tuning_params(spinand->spimem, tuning_params);
+	if (ret)
+		goto err_free_tuning_params;
+
+	/*
+	 * TODO:
+	 * Write the PHY pattern to cache using spinand_write_to_cache_op()
+	 * and readback pattern from cache during tuning instead of using up
+	 * the flash's space.
+	 *
+	 * For SPI NOR:
+	 * Things remain same as done here, the PHY pattern will be preflashed to
+	 * at an offset and will be readback during tuning.
+	 */
+
+	pageoffs = nanddev_offs_to_pos(nand, part->offset, &page_pos);
+	page_req.pos = page_pos;
+
+	read_page_op = *spinand->op_templates.read_cache;
+	read_page_op.addr.val = pageoffs;
+	read_page_op.data.nbytes = tuning_params->pattern_size;
+
+	ret = spinand_load_page_op(spinand, &page_req);
+	if (ret)
+		goto err_free_tuning_params;
+
+	ret = spinand_wait(spinand, SPINAND_READ_INITIAL_DELAY_US,
+			   SPINAND_READ_POLL_DELAY_US, &status);
+	if (ret < 0)
+		goto err_free_tuning_params;
+
+	spinand_ondie_ecc_save_status(nand, status);
+	ret = spi_mem_execute_tuning(spinand->spimem, &read_page_op);
+
+	/*
+	 * TODO:
+	 * Fallback to a lower frequency and a less dummy cycle in case of
+	 * PHY tuning failure
+	 */
+
+err_free_tuning_params:
+	kfree(tuning_params);
+	return ret;
+}
+
 static int spinand_init(struct spinand_device *spinand)
 {
 	struct device *dev = &spinand->spimem->spi->dev;
@@ -1551,6 +1611,7 @@ static int spinand_init(struct spinand_device *spinand)
 	mtd->_erase = spinand_mtd_erase;
 	mtd->_max_bad_blocks = nanddev_mtd_max_bad_blocks;
 	mtd->_resume = spinand_mtd_resume;
+	mtd->_execute_tuning = spinand_mtd_execute_tuning;
 
 	if (spinand_user_otp_size(spinand) || spinand_fact_otp_size(spinand)) {
 		ret = spinand_set_mtd_otp_ops(spinand);
diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h
index 8d10d9d2e830..5ac8dc02280d 100644
--- a/include/linux/mtd/mtd.h
+++ b/include/linux/mtd/mtd.h
@@ -355,6 +355,7 @@ struct mtd_info {
 	int (*_suspend) (struct mtd_info *mtd);
 	void (*_resume) (struct mtd_info *mtd);
 	void (*_reboot) (struct mtd_info *mtd);
+	int (*_execute_tuning) (struct mtd_info *mtd, struct mtd_part *part);
 	/*
 	 * If the driver is something smart, like UBI, it may need to maintain
 	 * its own reference counting. The below functions are only for driver.
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [RFC PATCH 04/10] mtd: mtdcore: Call mtd_execute_tuning during mtd_register
  2025-08-11 19:32 [RFC PATCH 00/10] SPINAND PHY Tuning Series Santhosh Kumar K
                   ` (2 preceding siblings ...)
  2025-08-11 19:32 ` [RFC PATCH 03/10] mtd: nand: spi: Introduce _execute_tuning for mtd devices Santhosh Kumar K
@ 2025-08-11 19:32 ` Santhosh Kumar K
  2025-08-11 19:32 ` [RFC PATCH 05/10] spi: cadence-quadspi: Move cqspi_readdata_capture() above all operations Santhosh Kumar K
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 16+ messages in thread
From: Santhosh Kumar K @ 2025-08-11 19:32 UTC (permalink / raw)
  To: miquel.raynal, richard, vigneshr, broonie, tudor.ambarus,
	pratyush, mwalle, p-mantena
  Cc: linux-spi, linux-mtd, linux-kernel, s-k6, a-dutta, u-kumar1,
	praneeth

Call mtd_execute_tuning() during the mtd_register() which redirects to
the flash specific tuning procedure to run at higher frequencies.

Signed-off-by: Santhosh Kumar K <s-k6@ti.com>
---
 drivers/mtd/mtdcore.c | 19 +++++++++++++++++++
 drivers/mtd/mtdcore.h |  1 +
 2 files changed, 20 insertions(+)

diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 5ba9a741f5ac..f50cebcdb670 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -1023,6 +1023,22 @@ static int mtd_otp_nvmem_add(struct mtd_info *mtd)
 	return dev_err_probe(dev, err, "Failed to register OTP NVMEM device\n");
 }
 
+int mtd_execute_tuning(struct mtd_info *mtd)
+{
+	struct mtd_part *part;
+
+	list_for_each_entry(part, &mtd->partitions, node) {
+		struct mtd_info *part_info =
+			container_of(part, struct mtd_info, part);
+		if (part_info->name &&
+		    !strcmp(part_info->name, "ospi.phypattern")) {
+			return mtd->_execute_tuning(mtd, part);
+		}
+	}
+
+	return -ENODEV;
+}
+
 /**
  * mtd_device_parse_register - parse partitions and register an MTD device.
  *
@@ -1087,6 +1103,9 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
 	if (ret)
 		goto out;
 
+	if (mtd_execute_tuning(mtd) == -ENODEV)
+		dev_info(&mtd->dev, "PHY pattern partition not found\n");
+
 	/*
 	 * FIXME: some drivers unfortunately call this function more than once.
 	 * So we have to check if we've already assigned the reboot notifier.
diff --git a/drivers/mtd/mtdcore.h b/drivers/mtd/mtdcore.h
index b014861a06a6..04055fcd2df8 100644
--- a/drivers/mtd/mtdcore.h
+++ b/drivers/mtd/mtdcore.h
@@ -13,6 +13,7 @@ int del_mtd_device(struct mtd_info *mtd);
 int add_mtd_partitions(struct mtd_info *, const struct mtd_partition *, int);
 int del_mtd_partitions(struct mtd_info *);
 void release_mtd_partition(struct mtd_info *mtd);
+int mtd_execute_tuning(struct mtd_info *mtd);
 
 struct mtd_partitions;
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [RFC PATCH 05/10] spi: cadence-quadspi: Move cqspi_readdata_capture() above all operations
  2025-08-11 19:32 [RFC PATCH 00/10] SPINAND PHY Tuning Series Santhosh Kumar K
                   ` (3 preceding siblings ...)
  2025-08-11 19:32 ` [RFC PATCH 04/10] mtd: mtdcore: Call mtd_execute_tuning during mtd_register Santhosh Kumar K
@ 2025-08-11 19:32 ` Santhosh Kumar K
  2025-08-11 19:32 ` [RFC PATCH 06/10] spi: cadence-quadspi: Use BIT() macro for CQSPI_REG_READCAPTURE_BYPASS Santhosh Kumar K
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 16+ messages in thread
From: Santhosh Kumar K @ 2025-08-11 19:32 UTC (permalink / raw)
  To: miquel.raynal, richard, vigneshr, broonie, tudor.ambarus,
	pratyush, mwalle, p-mantena
  Cc: linux-spi, linux-mtd, linux-kernel, s-k6, a-dutta, u-kumar1,
	praneeth

Move the cqspi_readdata_capture() above all the read and write
operations as PHY can be enabled for writes as well, in future.

Signed-off-by: Santhosh Kumar K <s-k6@ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 46 +++++++++++++++----------------
 1 file changed, 23 insertions(+), 23 deletions(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index bb90fbdd641f..da5823bcc1ea 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -444,6 +444,29 @@ static int cqspi_wait_idle(struct cqspi_st *cqspi)
 	}
 }
 
+static void cqspi_readdata_capture(struct cqspi_st *cqspi,
+				   const bool bypass,
+				   const unsigned int delay)
+{
+	void __iomem *reg_base = cqspi->iobase;
+	unsigned int reg;
+
+	reg = readl(reg_base + CQSPI_REG_READCAPTURE);
+
+	if (bypass)
+		reg |= (1 << CQSPI_REG_READCAPTURE_BYPASS_LSB);
+	else
+		reg &= ~(1 << CQSPI_REG_READCAPTURE_BYPASS_LSB);
+
+	reg &= ~(CQSPI_REG_READCAPTURE_DELAY_MASK
+		 << CQSPI_REG_READCAPTURE_DELAY_LSB);
+
+	reg |= (delay & CQSPI_REG_READCAPTURE_DELAY_MASK)
+		<< CQSPI_REG_READCAPTURE_DELAY_LSB;
+
+	writel(reg, reg_base + CQSPI_REG_READCAPTURE);
+}
+
 static int cqspi_exec_flash_cmd(struct cqspi_st *cqspi, unsigned int reg)
 {
 	void __iomem *reg_base = cqspi->iobase;
@@ -1259,29 +1282,6 @@ static void cqspi_config_baudrate_div(struct cqspi_st *cqspi)
 	writel(reg, reg_base + CQSPI_REG_CONFIG);
 }
 
-static void cqspi_readdata_capture(struct cqspi_st *cqspi,
-				   const bool bypass,
-				   const unsigned int delay)
-{
-	void __iomem *reg_base = cqspi->iobase;
-	unsigned int reg;
-
-	reg = readl(reg_base + CQSPI_REG_READCAPTURE);
-
-	if (bypass)
-		reg |= (1 << CQSPI_REG_READCAPTURE_BYPASS_LSB);
-	else
-		reg &= ~(1 << CQSPI_REG_READCAPTURE_BYPASS_LSB);
-
-	reg &= ~(CQSPI_REG_READCAPTURE_DELAY_MASK
-		 << CQSPI_REG_READCAPTURE_DELAY_LSB);
-
-	reg |= (delay & CQSPI_REG_READCAPTURE_DELAY_MASK)
-		<< CQSPI_REG_READCAPTURE_DELAY_LSB;
-
-	writel(reg, reg_base + CQSPI_REG_READCAPTURE);
-}
-
 static void cqspi_configure(struct cqspi_flash_pdata *f_pdata,
 			    unsigned long sclk)
 {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [RFC PATCH 06/10] spi: cadence-quadspi: Use BIT() macro for CQSPI_REG_READCAPTURE_BYPASS
  2025-08-11 19:32 [RFC PATCH 00/10] SPINAND PHY Tuning Series Santhosh Kumar K
                   ` (4 preceding siblings ...)
  2025-08-11 19:32 ` [RFC PATCH 05/10] spi: cadence-quadspi: Move cqspi_readdata_capture() above all operations Santhosh Kumar K
@ 2025-08-11 19:32 ` Santhosh Kumar K
  2025-08-11 19:32 ` [RFC PATCH 07/10] spi: cadence-quadspi: Enable PHY for aligned DAC reads Santhosh Kumar K
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 16+ messages in thread
From: Santhosh Kumar K @ 2025-08-11 19:32 UTC (permalink / raw)
  To: miquel.raynal, richard, vigneshr, broonie, tudor.ambarus,
	pratyush, mwalle, p-mantena
  Cc: linux-spi, linux-mtd, linux-kernel, s-k6, a-dutta, u-kumar1,
	praneeth

Simplify CQSPI_REG_READCAPTURE_BYPASS macro and add comment on Bypass
bit.

Signed-off-by: Santhosh Kumar K <s-k6@ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index da5823bcc1ea..1d86a7bc405e 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -180,7 +180,7 @@ struct cqspi_driver_platdata {
 #define CQSPI_REG_DELAY_TSHSL_MASK		0xFF
 
 #define CQSPI_REG_READCAPTURE			0x10
-#define CQSPI_REG_READCAPTURE_BYPASS_LSB	0
+#define CQSPI_REG_READCAPTURE_BYPASS		BIT(0)
 #define CQSPI_REG_READCAPTURE_DELAY_LSB		1
 #define CQSPI_REG_READCAPTURE_DELAY_MASK	0xF
 
@@ -453,10 +453,15 @@ static void cqspi_readdata_capture(struct cqspi_st *cqspi,
 
 	reg = readl(reg_base + CQSPI_REG_READCAPTURE);
 
+	/*
+	 * Bypass bit - to enable/disable the signal from adapted loopback
+	 * clock circuit which is driven into Rx DLL and is used for data
+	 * capturing rather than internally generated ref_clk.
+	 */
 	if (bypass)
-		reg |= (1 << CQSPI_REG_READCAPTURE_BYPASS_LSB);
+		reg |= CQSPI_REG_READCAPTURE_BYPASS;
 	else
-		reg &= ~(1 << CQSPI_REG_READCAPTURE_BYPASS_LSB);
+		reg &= ~CQSPI_REG_READCAPTURE_BYPASS;
 
 	reg &= ~(CQSPI_REG_READCAPTURE_DELAY_MASK
 		 << CQSPI_REG_READCAPTURE_DELAY_LSB);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [RFC PATCH 07/10] spi: cadence-quadspi: Enable PHY for aligned DAC reads
  2025-08-11 19:32 [RFC PATCH 00/10] SPINAND PHY Tuning Series Santhosh Kumar K
                   ` (5 preceding siblings ...)
  2025-08-11 19:32 ` [RFC PATCH 06/10] spi: cadence-quadspi: Use BIT() macro for CQSPI_REG_READCAPTURE_BYPASS Santhosh Kumar K
@ 2025-08-11 19:32 ` Santhosh Kumar K
  2025-08-11 19:32 ` [RFC PATCH 08/10] spi: cadence-quadspi: Enable PHY for data writes Santhosh Kumar K
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 16+ messages in thread
From: Santhosh Kumar K @ 2025-08-11 19:32 UTC (permalink / raw)
  To: miquel.raynal, richard, vigneshr, broonie, tudor.ambarus,
	pratyush, mwalle, p-mantena
  Cc: linux-spi, linux-mtd, linux-kernel, s-k6, a-dutta, u-kumar1,
	praneeth

From: Pratyush Yadav <pratyush@kernel.org>

PHY reads only work at an address that is 16-byte aligned and of size
that is a multiple of 16 bytes. So, enable PHY for the aligned middle
part and read the unaligned start and end part without PHY.

Also, in 8D mode an odd number of bytes cannot be read from the flash
since they would result in half a cycle being left over. memcpy_fromio()
doesn't guarantee of access width size. So, replace memcpy_fromio() with
cqspi_memcpy_fromio().

Signed-off-by: Pratyush Yadav <pratyush@kernel.org>
Signed-off-by: Santhosh Kumar K <s-k6@ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 235 +++++++++++++++++++++++++++---
 1 file changed, 215 insertions(+), 20 deletions(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index 1d86a7bc405e..2096027bca4c 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -61,15 +61,23 @@ enum {
 
 struct cqspi_st;
 
+struct phy_setting {
+	u8	rx;
+	u8	tx;
+	u8	read_delay;
+};
+
 struct cqspi_flash_pdata {
-	struct cqspi_st	*cqspi;
-	u32		clk_rate;
-	u32		read_delay;
-	u32		tshsl_ns;
-	u32		tsd2d_ns;
-	u32		tchsh_ns;
-	u32		tslch_ns;
-	u8		cs;
+	struct cqspi_st		*cqspi;
+	u32			clk_rate;
+	u32			read_delay;
+	u32			tshsl_ns;
+	u32			tsd2d_ns;
+	u32			tchsh_ns;
+	u32			tslch_ns;
+	u8			cs;
+	bool			use_phy;
+	struct phy_setting	phy_setting;
 };
 
 struct cqspi_st {
@@ -126,6 +134,7 @@ struct cqspi_driver_platdata {
 #define CQSPI_TIMEOUT_MS			500
 #define CQSPI_READ_TIMEOUT_MS			10
 #define CQSPI_BUSYWAIT_TIMEOUT_US		500
+#define CQSPI_PHY_FREQUENCY			166000000
 
 /* Runtime_pm autosuspend delay */
 #define CQSPI_AUTOSUSPEND_TIMEOUT		2000
@@ -139,12 +148,14 @@ struct cqspi_driver_platdata {
 /* Register map */
 #define CQSPI_REG_CONFIG			0x00
 #define CQSPI_REG_CONFIG_ENABLE_MASK		BIT(0)
+#define CQSPI_REG_CONFIG_PHY_EN			BIT(3)
 #define CQSPI_REG_CONFIG_ENB_DIR_ACC_CTRL	BIT(7)
 #define CQSPI_REG_CONFIG_DECODE_MASK		BIT(9)
 #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_PHY_PIPELINE		BIT(25)
 #define CQSPI_REG_CONFIG_DUAL_OPCODE		BIT(30)
 #define CQSPI_REG_CONFIG_IDLE_LSB		31
 #define CQSPI_REG_CONFIG_CHIPSELECT_MASK	0xF
@@ -472,6 +483,65 @@ static void cqspi_readdata_capture(struct cqspi_st *cqspi,
 	writel(reg, reg_base + CQSPI_REG_READCAPTURE);
 }
 
+static void cqspi_phy_enable(struct cqspi_flash_pdata *f_pdata, bool enable)
+{
+	struct cqspi_st *cqspi = f_pdata->cqspi;
+	void __iomem *reg_base = cqspi->iobase;
+	u32 reg;
+	u8 dummy;
+
+	if (enable) {
+		cqspi_readdata_capture(cqspi, true,
+				       f_pdata->phy_setting.read_delay);
+
+		reg = readl(reg_base + CQSPI_REG_CONFIG);
+		reg |= CQSPI_REG_CONFIG_PHY_EN | CQSPI_REG_CONFIG_PHY_PIPELINE;
+		writel(reg, reg_base + CQSPI_REG_CONFIG);
+
+		/*
+		 * Reduce dummy cycle by 1. This is a requirement of PHY mode
+		 * operation for correctly reading the data.
+		 */
+		reg = readl(reg_base + CQSPI_REG_RD_INSTR);
+		dummy = FIELD_GET(CQSPI_REG_RD_INSTR_DUMMY_MASK
+					  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+				  reg);
+		dummy--;
+		reg &= ~(CQSPI_REG_RD_INSTR_DUMMY_MASK
+			 << CQSPI_REG_RD_INSTR_DUMMY_LSB);
+		reg |= FIELD_PREP(CQSPI_REG_RD_INSTR_DUMMY_MASK
+					  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+				  dummy);
+		writel(reg, reg_base + CQSPI_REG_RD_INSTR);
+	} else {
+		cqspi_readdata_capture(cqspi, !cqspi->rclk_en,
+				       f_pdata->read_delay);
+
+		reg = readl(reg_base + CQSPI_REG_CONFIG);
+		reg &= ~(CQSPI_REG_CONFIG_PHY_EN |
+			 CQSPI_REG_CONFIG_PHY_PIPELINE);
+		writel(reg, reg_base + CQSPI_REG_CONFIG);
+
+		/*
+		 * Dummy cycles were decremented when enabling PHY. Increment
+		 * dummy cycle by 1 to restore the original value.
+		 */
+		reg = readl(reg_base + CQSPI_REG_RD_INSTR);
+		dummy = FIELD_GET(CQSPI_REG_RD_INSTR_DUMMY_MASK
+					  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+				  reg);
+		dummy++;
+		reg &= ~(CQSPI_REG_RD_INSTR_DUMMY_MASK
+			 << CQSPI_REG_RD_INSTR_DUMMY_LSB);
+		reg |= FIELD_PREP(CQSPI_REG_RD_INSTR_DUMMY_MASK
+					  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+				  dummy);
+		writel(reg, reg_base + CQSPI_REG_RD_INSTR);
+	}
+
+	cqspi_wait_idle(cqspi);
+}
+
 static int cqspi_exec_flash_cmd(struct cqspi_st *cqspi, unsigned int reg)
 {
 	void __iomem *reg_base = cqspi->iobase;
@@ -1346,6 +1416,35 @@ static ssize_t cqspi_write(struct cqspi_flash_pdata *f_pdata,
 	return cqspi_indirect_write_execute(f_pdata, to, buf, len);
 }
 
+static bool cqspi_phy_op_eligible_sdr(struct cqspi_flash_pdata *f_pdata,
+				      const struct spi_mem_op *op)
+{
+	if (f_pdata->clk_rate != CQSPI_PHY_FREQUENCY)
+		return false;
+	if (op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr)
+		return false;
+	if (!(op->addr.nbytes) || op->addr.buswidth < 1)
+		return false;
+	if (!(op->dummy.nbytes) || op->dummy.buswidth < 1)
+		return false;
+	if (!(op->data.nbytes) || op->data.buswidth < 1)
+		return false;
+
+	return true;
+}
+
+static bool cqspi_use_phy(struct cqspi_flash_pdata *f_pdata,
+			  const struct spi_mem_op *op)
+{
+	if (!f_pdata->use_phy)
+		return false;
+
+	if (op->data.nbytes < 16)
+		return false;
+
+	return cqspi_phy_op_eligible_sdr(f_pdata, op);
+}
+
 static void cqspi_rx_dma_callback(void *param)
 {
 	struct cqspi_st *cqspi = param;
@@ -1353,8 +1452,8 @@ static void cqspi_rx_dma_callback(void *param)
 	complete(&cqspi->rx_dma_complete);
 }
 
-static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata,
-				     u_char *buf, loff_t from, size_t len)
+static int cqspi_direct_read_dma(struct cqspi_flash_pdata *f_pdata, u_char *buf,
+				 loff_t from, size_t len)
 {
 	struct cqspi_st *cqspi = f_pdata->cqspi;
 	struct device *dev = &cqspi->pdev->dev;
@@ -1366,19 +1465,14 @@ static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata,
 	dma_addr_t dma_dst;
 	struct device *ddev;
 
-	if (!cqspi->rx_chan || !virt_addr_valid(buf)) {
-		memcpy_fromio(buf, cqspi->ahb_base + from, len);
-		return 0;
-	}
-
 	ddev = cqspi->rx_chan->device->dev;
 	dma_dst = dma_map_single(ddev, buf, len, DMA_FROM_DEVICE);
 	if (dma_mapping_error(ddev, dma_dst)) {
 		dev_err(dev, "dma mapping failed\n");
 		return -ENOMEM;
 	}
-	tx = dmaengine_prep_dma_memcpy(cqspi->rx_chan, dma_dst, dma_src,
-				       len, flags);
+	tx = dmaengine_prep_dma_memcpy(cqspi->rx_chan, dma_dst, dma_src, len,
+				       flags);
 	if (!tx) {
 		dev_err(dev, "device_prep_dma_memcpy error\n");
 		ret = -EIO;
@@ -1398,8 +1492,9 @@ static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata,
 	}
 
 	dma_async_issue_pending(cqspi->rx_chan);
-	if (!wait_for_completion_timeout(&cqspi->rx_dma_complete,
-					 msecs_to_jiffies(max_t(size_t, len, 500)))) {
+	if (!wait_for_completion_timeout(
+		    &cqspi->rx_dma_complete,
+		    msecs_to_jiffies(max_t(size_t, len, 500)))) {
 		dmaengine_terminate_sync(cqspi->rx_chan);
 		dev_err(dev, "DMA wait_for_completion_timeout\n");
 		ret = -ETIMEDOUT;
@@ -1412,6 +1507,106 @@ static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata,
 	return ret;
 }
 
+static void cqspi_memcpy_fromio(const struct spi_mem_op *op, void *to,
+				const void __iomem *from, size_t count)
+{
+	if (op->data.buswidth == 8 && op->data.dtr) {
+		/*
+		 * 8D-8D-8D ops with odd length should be rejected by
+		 * supports_op() so no need to worry about that.
+		 */
+		if (count && !IS_ALIGNED((unsigned long)from, 4)) {
+			*(u16 *)to = __raw_readw(from);
+			from += 2;
+			to += 2;
+			count -= 2;
+		}
+
+		/*
+		 * The controller can work with both 32-bit and 64-bit
+		 * platforms. 32-bit platforms won't have a readq. So use a
+		 * readl instead.
+		 */
+		if (count >= 4) {
+			int len = round_down(count, 4);
+
+			memcpy_fromio(to, from, len);
+			from += len;
+			to += len;
+			count -= len;
+		}
+
+		if (count) {
+			*(u16 *)to = __raw_readw(from);
+			from += 2;
+			to += 2;
+			count -= 2;
+		}
+
+		return;
+	}
+
+	memcpy_fromio(to, from, count);
+}
+
+static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata,
+				     const struct spi_mem_op *op)
+{
+	struct cqspi_st *cqspi = f_pdata->cqspi;
+	loff_t from = op->addr.val;
+	loff_t from_aligned, to_aligned;
+	size_t len = op->data.nbytes;
+	size_t len_aligned;
+	u_char *buf = op->data.buf.in;
+	int ret;
+
+	if (!cqspi->rx_chan || !virt_addr_valid(buf) || len <= 16) {
+		cqspi_memcpy_fromio(op, buf, cqspi->ahb_base + from, len);
+		return 0;
+	}
+
+	if (!cqspi_use_phy(f_pdata, op))
+		return cqspi_direct_read_dma(f_pdata, buf, from, len);
+
+	/*
+	 * PHY reads must be 16-byte aligned, and they must be a multiple of 16
+	 * bytes.
+	 */
+	from_aligned = (from + 0xF) & ~0xF;
+	to_aligned = (from + len) & ~0xF;
+	len_aligned = to_aligned - from_aligned;
+
+	/* Read the unaligned part at the start. */
+	if (from != from_aligned) {
+		ret = cqspi_direct_read_dma(f_pdata, buf, from,
+					    from_aligned - from);
+		if (ret)
+			return ret;
+		buf += from_aligned - from;
+	}
+
+	if (len_aligned) {
+		cqspi_phy_enable(f_pdata, true);
+		ret = cqspi_direct_read_dma(f_pdata, buf, from_aligned,
+					    len_aligned);
+		cqspi_phy_enable(f_pdata, false);
+		if (ret)
+			return ret;
+		buf += len_aligned;
+	}
+
+	/* Now read the remaining part, if any. */
+	if (to_aligned != (from + len)) {
+		ret = cqspi_direct_read_dma(f_pdata, buf, to_aligned,
+					    (from + len) - to_aligned);
+		if (ret)
+			return ret;
+		buf += (from + len) - to_aligned;
+	}
+
+	return 0;
+}
+
 static ssize_t cqspi_read(struct cqspi_flash_pdata *f_pdata,
 			  const struct spi_mem_op *op)
 {
@@ -1428,7 +1623,7 @@ static ssize_t cqspi_read(struct cqspi_flash_pdata *f_pdata,
 		return ret;
 
 	if (cqspi->use_direct_mode && ((from + len) <= cqspi->ahb_size))
-		return cqspi_direct_read_execute(f_pdata, buf, from, len);
+		return cqspi_direct_read_execute(f_pdata, op);
 
 	if (cqspi->use_dma_read && ddata && ddata->indirect_read_dma &&
 	    virt_addr_valid(buf) && ((dma_align & CQSPI_DMA_UNALIGN) == 0))
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [RFC PATCH 08/10] spi: cadence-quadspi: Enable PHY for data writes
  2025-08-11 19:32 [RFC PATCH 00/10] SPINAND PHY Tuning Series Santhosh Kumar K
                   ` (6 preceding siblings ...)
  2025-08-11 19:32 ` [RFC PATCH 07/10] spi: cadence-quadspi: Enable PHY for aligned DAC reads Santhosh Kumar K
@ 2025-08-11 19:32 ` Santhosh Kumar K
  2025-08-11 19:32 ` [RFC PATCH 09/10] spi: cadence-quadspi: Implement PHY for higher frequencies in SDR mode Santhosh Kumar K
  2025-08-11 19:32 ` [RFC PATCH 10/10] spi: cadence-quadspi: Define cqspi_get_tuning_params() Santhosh Kumar K
  9 siblings, 0 replies; 16+ messages in thread
From: Santhosh Kumar K @ 2025-08-11 19:32 UTC (permalink / raw)
  To: miquel.raynal, richard, vigneshr, broonie, tudor.ambarus,
	pratyush, mwalle, p-mantena
  Cc: linux-spi, linux-mtd, linux-kernel, s-k6, a-dutta, u-kumar1,
	praneeth

PHY is tuned with optimal tuning point which allows controller to
run at higher speeds. Hence, increase the data writes' throughput in
OSPI/QSPI NAND flashes by enabling PHY for data writes to the NAND
flash devices.

The aim is to enable PHY only for the OSPI/QSPI NAND data writes,
so, exclude other operations like register writes to NAND flashes,
register and data writes to NOR flashes by introducing a check for
the 'n_tx' (op->data.nbytes) value before enabling.

Currently, OSPI/QSPI NOR's highest page size is 512 bytes, so, check
whether 'n_tx' is greater than or equal to 1024 and 'f_pdata->use_phy'
flag.

Signed-off-by: Santhosh Kumar K <s-k6@ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index 2096027bca4c..6c1159435577 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -1202,6 +1202,9 @@ static int cqspi_indirect_write_execute(struct cqspi_flash_pdata *f_pdata,
 	if (cqspi->apb_ahb_hazard)
 		readl(reg_base + CQSPI_REG_INDIRECTWR);
 
+	if (n_tx >= SZ_1K && f_pdata->use_phy)
+		cqspi_phy_enable(f_pdata, true);
+
 	while (remaining > 0) {
 		size_t write_words, mod_bytes;
 
@@ -1242,6 +1245,9 @@ static int cqspi_indirect_write_execute(struct cqspi_flash_pdata *f_pdata,
 		goto failwr;
 	}
 
+	if (n_tx >= SZ_1K && f_pdata->use_phy)
+		cqspi_phy_enable(f_pdata, false);
+
 	/* Disable interrupt. */
 	writel(0, reg_base + CQSPI_REG_IRQMASK);
 
@@ -1253,6 +1259,9 @@ static int cqspi_indirect_write_execute(struct cqspi_flash_pdata *f_pdata,
 	return 0;
 
 failwr:
+	if (n_tx >= SZ_1K && f_pdata->use_phy)
+		cqspi_phy_enable(f_pdata, false);
+
 	/* Disable interrupt. */
 	writel(0, reg_base + CQSPI_REG_IRQMASK);
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [RFC PATCH 09/10] spi: cadence-quadspi: Implement PHY for higher frequencies in SDR mode
  2025-08-11 19:32 [RFC PATCH 00/10] SPINAND PHY Tuning Series Santhosh Kumar K
                   ` (7 preceding siblings ...)
  2025-08-11 19:32 ` [RFC PATCH 08/10] spi: cadence-quadspi: Enable PHY for data writes Santhosh Kumar K
@ 2025-08-11 19:32 ` Santhosh Kumar K
  2025-08-11 19:32 ` [RFC PATCH 10/10] spi: cadence-quadspi: Define cqspi_get_tuning_params() Santhosh Kumar K
  9 siblings, 0 replies; 16+ messages in thread
From: Santhosh Kumar K @ 2025-08-11 19:32 UTC (permalink / raw)
  To: miquel.raynal, richard, vigneshr, broonie, tudor.ambarus,
	pratyush, mwalle, p-mantena
  Cc: linux-spi, linux-mtd, linux-kernel, s-k6, a-dutta, u-kumar1,
	praneeth

With SDR mode, the controller can only process at 1/4th of the reference
clock without PHY. So, add a SDR based tuning algorithm to enable higher
speed operations. A known pattern (stress pattern) is flashed at a
location in the flash device, which will be later used to read back
and compare.

Iterate through read_delay from 0 to 4, find first tuning point by
setting tx=127. Find difference between rxlow and rxhigh and store it in
window1. Find second tuning point with read_delay incremented by 1 and
similarly calculate window2.

Compare window1 and window2 to finalise the optimal tuning point. Write
the final tuning point into PHY Configuration Register.

With this OSPI can operate at up-to 200MHz SDR mode with internal
loopback clocking scheme for sampling data inside the soft PHY

Tuning for DDR mode with DQS will be added a later point.

Signed-off-by: Santhosh Kumar K <s-k6@ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 447 ++++++++++++++++++++++++++++++
 1 file changed, 447 insertions(+)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index 6c1159435577..1626cb9a9700 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -78,6 +78,7 @@ struct cqspi_flash_pdata {
 	u8			cs;
 	bool			use_phy;
 	struct phy_setting	phy_setting;
+	struct spi_mem_op	phy_read_op;
 };
 
 struct cqspi_st {
@@ -135,6 +136,7 @@ struct cqspi_driver_platdata {
 #define CQSPI_READ_TIMEOUT_MS			10
 #define CQSPI_BUSYWAIT_TIMEOUT_US		500
 #define CQSPI_PHY_FREQUENCY			166000000
+#define CQSPI_DLL_TIMEOUT_US			300
 
 /* Runtime_pm autosuspend delay */
 #define CQSPI_AUTOSUSPEND_TIMEOUT		2000
@@ -194,6 +196,7 @@ struct cqspi_driver_platdata {
 #define CQSPI_REG_READCAPTURE_BYPASS		BIT(0)
 #define CQSPI_REG_READCAPTURE_DELAY_LSB		1
 #define CQSPI_REG_READCAPTURE_DELAY_MASK	0xF
+#define CQSPI_REG_READCAPTURE_EDGE		BIT(5)
 
 #define CQSPI_REG_SIZE				0x14
 #define CQSPI_REG_SIZE_ADDRESS_LSB		0
@@ -272,6 +275,26 @@ struct cqspi_driver_platdata {
 #define CQSPI_REG_POLLING_STATUS		0xB0
 #define CQSPI_REG_POLLING_STATUS_DUMMY_LSB	16
 
+#define CQSPI_REG_PHY_CONFIG			0xB4
+#define CQSPI_REG_PHY_CONFIG_RX_DEL_LSB		0
+#define CQSPI_REG_PHY_CONFIG_RX_DEL_MASK	0x7F
+#define CQSPI_REG_PHY_CONFIG_TX_DEL_LSB		16
+#define CQSPI_REG_PHY_CONFIG_TX_DEL_MASK	0x7F
+#define CQSPI_REG_PHY_CONFIG_DLL_RESET		BIT(30)
+#define CQSPI_REG_PHY_CONFIG_RESYNC		BIT(31)
+
+#define CQSPI_REG_PHY_DLL_MASTER		0xB8
+#define CQSPI_REG_PHY_DLL_MASTER_INIT_DELAY_LSB	0
+#define CQSPI_REG_PHY_DLL_MASTER_INIT_DELAY_VAL	16
+#define CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_LEN	0x7
+#define CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_LSB	20
+#define CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_3	0x2
+#define CQSPI_REG_PHY_DLL_MASTER_BYPASS		BIT(23)
+#define CQSPI_REG_PHY_DLL_MASTER_CYCLE		BIT(24)
+
+#define CQSPI_REG_DLL_OBS_LOW			0xBC
+#define CQSPI_REG_DLL_OBS_LOW_DLL_LOCK		BIT(0)
+#define CQSPI_REG_DLL_OBS_LOW_LOOPBACK_LOCK	BIT(15)
 #define CQSPI_REG_OP_EXT_LOWER			0xE0
 #define CQSPI_REG_OP_EXT_READ_LSB		24
 #define CQSPI_REG_OP_EXT_WRITE_LSB		16
@@ -317,6 +340,46 @@ struct cqspi_driver_platdata {
 
 #define CQSPI_REG_VERSAL_DMA_VAL		0x602
 
+#define CQSPI_PHY_INIT_RD			1
+#define CQSPI_PHY_MAX_RD			4
+#define CQSPI_PHY_MAX_DELAY			127
+#define CQSPI_PHY_DDR_SEARCH_STEP		4
+#define CQSPI_PHY_MAX_RX			63
+#define CQSPI_PHY_MAX_TX			63
+#define CQSPI_PHY_TX_LOOKUP_LOW_START		28
+#define CQSPI_PHY_TX_LOOKUP_LOW_END		48
+#define CQSPI_PHY_TX_LOOKUP_HIGH_START		60
+#define CQSPI_PHY_TX_LOOKUP_HIGH_END		96
+#define CQSPI_PHY_RX_LOW_SEARCH_START		0
+#define CQSPI_PHY_RX_LOW_SEARCH_END		40
+#define CQSPI_PHY_RX_HIGH_SEARCH_START		24
+#define CQSPI_PHY_RX_HIGH_SEARCH_END		127
+#define CQSPI_PHY_TX_LOW_SEARCH_START		0
+#define CQSPI_PHY_TX_LOW_SEARCH_END		64
+#define CQSPI_PHY_TX_HIGH_SEARCH_START		78
+#define CQSPI_PHY_TX_HIGH_SEARCH_END		127
+#define CQSPI_PHY_SEARCH_OFFSET			8
+
+#define CQSPI_PHY_DEFAULT_TEMP		45
+#define CQSPI_PHY_MIN_TEMP		-45
+#define CQSPI_PHY_MAX_TEMP		130
+#define CQSPI_PHY_MID_TEMP \
+	(CQSPI_PHY_MIN_TEMP + ((CQSPI_PHY_MAX_TEMP - CQSPI_PHY_MIN_TEMP) / 2))
+
+static u8 phy_tuning_pattern[] = {
+	0xFE, 0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xFE, 0xFE, 0x01,
+	0x01, 0x01, 0x01, 0x00, 0x00, 0xFE, 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF,
+	0xFF, 0x00, 0x00, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFE,
+	0xFE, 0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFE, 0x00, 0xFE, 0xFE, 0x01,
+	0x01, 0x01, 0x01, 0xFE, 0x00, 0xFE, 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF,
+	0xFF, 0xFE, 0x00, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xFE,
+	0xFE, 0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xFE, 0xFE, 0xFE, 0x01,
+	0x01, 0x01, 0x01, 0x00, 0xFE, 0xFE, 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF,
+	0xFF, 0x00, 0xFE, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFE, 0xFE,
+	0xFE, 0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFE, 0xFE, 0xFE, 0xFE, 0x01,
+	0x01, 0x01, 0x01, 0xFE, 0xFE, 0xFE, 0xFE, 0x01,
+};
+
 static int cqspi_wait_for_bit(const struct cqspi_driver_platdata *ddata,
 			      void __iomem *reg, const u32 mask, bool clr,
 			      bool busywait)
@@ -455,6 +518,279 @@ static int cqspi_wait_idle(struct cqspi_st *cqspi)
 	}
 }
 
+static void cqspi_set_tx_dll(void __iomem *reg_base, u8 dll)
+{
+	unsigned int reg;
+
+	reg = readl(reg_base + CQSPI_REG_PHY_CONFIG);
+	reg &= ~(CQSPI_REG_PHY_CONFIG_TX_DEL_MASK
+		 << CQSPI_REG_PHY_CONFIG_TX_DEL_LSB);
+	reg |= (dll & CQSPI_REG_PHY_CONFIG_TX_DEL_MASK)
+	       << CQSPI_REG_PHY_CONFIG_TX_DEL_LSB;
+	reg |= CQSPI_REG_PHY_CONFIG_RESYNC;
+	writel(reg, reg_base + CQSPI_REG_PHY_CONFIG);
+}
+
+static void cqspi_set_rx_dll(void __iomem *reg_base, u8 dll)
+{
+	unsigned int reg;
+
+	reg = readl(reg_base + CQSPI_REG_PHY_CONFIG);
+	reg &= ~(CQSPI_REG_PHY_CONFIG_RX_DEL_MASK
+		 << CQSPI_REG_PHY_CONFIG_RX_DEL_LSB);
+	reg |= (dll & CQSPI_REG_PHY_CONFIG_RX_DEL_MASK)
+	       << CQSPI_REG_PHY_CONFIG_RX_DEL_LSB;
+	reg |= CQSPI_REG_PHY_CONFIG_RESYNC;
+	writel(reg, reg_base + CQSPI_REG_PHY_CONFIG);
+}
+
+static int cqspi_resync_dll(struct cqspi_st *cqspi)
+{
+	void __iomem *reg_base = cqspi->iobase;
+	unsigned int reg;
+	int ret;
+
+	ret = cqspi_wait_idle(cqspi);
+
+	if (!ret) {
+		reg = readl(reg_base + CQSPI_REG_CONFIG);
+		reg &= ~(CQSPI_REG_CONFIG_ENABLE_MASK);
+		writel(reg, reg_base + CQSPI_REG_CONFIG);
+
+		reg = readl(reg_base + CQSPI_REG_PHY_CONFIG);
+		reg &= ~(CQSPI_REG_PHY_CONFIG_DLL_RESET |
+			 CQSPI_REG_PHY_CONFIG_RESYNC);
+		writel(reg, reg_base + CQSPI_REG_PHY_CONFIG);
+
+		reg = readl(reg_base + CQSPI_REG_PHY_DLL_MASTER);
+		reg |= (CQSPI_REG_PHY_DLL_MASTER_INIT_DELAY_VAL
+			<< CQSPI_REG_PHY_DLL_MASTER_INIT_DELAY_LSB);
+		writel(reg, reg_base + CQSPI_REG_PHY_DLL_MASTER);
+
+		reg = readl(reg_base + CQSPI_REG_PHY_CONFIG);
+		reg |= CQSPI_REG_PHY_CONFIG_DLL_RESET;
+		writel(reg, reg_base + CQSPI_REG_PHY_CONFIG);
+
+		readl_poll_timeout(reg_base + CQSPI_REG_DLL_OBS_LOW, reg,
+				   !(reg &= CQSPI_REG_DLL_OBS_LOW_DLL_LOCK), 0,
+				   CQSPI_DLL_TIMEOUT_US);
+
+		readl_poll_timeout(reg_base + CQSPI_REG_DLL_OBS_LOW, reg,
+				   !(reg &= CQSPI_REG_DLL_OBS_LOW_LOOPBACK_LOCK),
+				   0, CQSPI_DLL_TIMEOUT_US);
+
+		reg = readl(reg_base + CQSPI_REG_PHY_CONFIG);
+		reg |= CQSPI_REG_PHY_CONFIG_RESYNC;
+		writel(reg, reg_base + CQSPI_REG_PHY_CONFIG);
+
+		reg = readl(reg_base + CQSPI_REG_CONFIG);
+		reg |= CQSPI_REG_CONFIG_ENABLE_MASK;
+		writel(reg, reg_base + CQSPI_REG_CONFIG);
+	}
+
+	return ret;
+}
+
+static int cqspi_phy_apply_setting(struct cqspi_flash_pdata *f_pdata,
+				   struct phy_setting *phy)
+{
+	struct cqspi_st *cqspi = f_pdata->cqspi;
+	unsigned int reg;
+
+	reg = readl(cqspi->iobase + CQSPI_REG_READCAPTURE);
+	reg |= CQSPI_REG_READCAPTURE_EDGE;
+	writel(reg, cqspi->iobase + CQSPI_REG_READCAPTURE);
+
+	cqspi_set_rx_dll(cqspi->iobase, phy->rx);
+	cqspi_set_tx_dll(cqspi->iobase, phy->tx);
+	f_pdata->phy_setting.read_delay = phy->read_delay;
+
+	return cqspi_resync_dll(cqspi);
+}
+
+static int cqspi_phy_check_pattern(struct cqspi_flash_pdata *f_pdata,
+				   struct spi_mem *mem)
+{
+	struct spi_mem_op op = f_pdata->phy_read_op;
+	u8 *read_data;
+	int ret;
+
+	read_data = kmalloc(sizeof(phy_tuning_pattern), GFP_KERNEL);
+	if (!read_data)
+		return -ENOMEM;
+
+	op.data.buf.in = read_data;
+	op.data.nbytes = sizeof(phy_tuning_pattern);
+
+	ret = spi_mem_exec_op(mem, &op);
+	if (ret)
+		goto out;
+
+	if (memcmp(read_data, phy_tuning_pattern,
+		   ARRAY_SIZE(phy_tuning_pattern))) {
+		ret = -EAGAIN;
+		goto out;
+	}
+
+	ret = 0;
+
+out:
+	kfree(read_data);
+	return ret;
+}
+
+static int cqspi_find_rx_low_sdr(struct cqspi_flash_pdata *f_pdata,
+				 struct spi_mem *mem, struct phy_setting *phy)
+{
+	struct device *dev = &f_pdata->cqspi->pdev->dev;
+	int ret;
+
+	phy->rx = 0;
+	do {
+		ret = cqspi_phy_apply_setting(f_pdata, phy);
+		if (!ret) {
+			ret = cqspi_phy_check_pattern(f_pdata, mem);
+			if (!ret)
+				return 0;
+		}
+		phy->rx++;
+	} while (phy->rx < CQSPI_PHY_MAX_DELAY - 1);
+
+	dev_dbg(dev, "Unable to find RX low\n");
+	return -ENOENT;
+}
+
+static int cqspi_find_rx_high_sdr(struct cqspi_flash_pdata *f_pdata,
+				  struct spi_mem *mem, struct phy_setting *phy,
+				  u8 lowerbound)
+{
+	struct device *dev = &f_pdata->cqspi->pdev->dev;
+	int ret;
+
+	phy->rx = CQSPI_PHY_MAX_DELAY;
+	do {
+		ret = cqspi_phy_apply_setting(f_pdata, phy);
+		if (!ret) {
+			ret = cqspi_phy_check_pattern(f_pdata, mem);
+			if (!ret)
+				return 0;
+		}
+		phy->rx--;
+	} while (phy->rx > lowerbound);
+
+	dev_dbg(dev, "Unable to find RX high\n");
+	return -ENOENT;
+}
+
+static void cqspi_phy_reset_setting(struct phy_setting *phy)
+{
+	phy->rx = 0;
+	phy->tx = 127;
+	phy->read_delay = 0;
+}
+
+static int cqspi_phy_tuning_sdr(struct cqspi_flash_pdata *f_pdata,
+				struct spi_mem *mem)
+{
+	struct cqspi_st *cqspi = f_pdata->cqspi;
+	struct device *dev = &cqspi->pdev->dev;
+	struct phy_setting rxlow, rxhigh, first, second, final;
+	char window1 = 0;
+	char window2 = 0;
+	int ret;
+
+	f_pdata->use_phy = true;
+	cqspi_phy_reset_setting(&rxlow);
+	cqspi_phy_reset_setting(&rxhigh);
+	cqspi_phy_reset_setting(&first);
+
+	do {
+		ret = cqspi_find_rx_low_sdr(f_pdata, mem, &rxlow);
+
+		if (ret)
+			rxlow.read_delay++;
+	} while (ret && rxlow.read_delay <= CQSPI_PHY_MAX_RD);
+
+	rxhigh.read_delay = rxlow.read_delay;
+	ret = cqspi_find_rx_high_sdr(f_pdata, mem, &rxhigh, rxlow.rx);
+	if (ret)
+		goto out;
+
+	first.read_delay = rxlow.read_delay;
+	window1 = rxhigh.rx - rxlow.rx;
+	first.rx = rxlow.rx + (window1 / 2);
+
+	dev_dbg(dev, "First tuning point: RX: %d TX: %d RD: %d\n", first.rx,
+		first.tx, first.read_delay);
+	ret = cqspi_phy_apply_setting(f_pdata, &first);
+	if (!ret)
+		ret = cqspi_phy_check_pattern(f_pdata, mem);
+
+	if (ret || first.read_delay > CQSPI_PHY_MAX_RD)
+		goto out;
+
+	cqspi_phy_reset_setting(&rxlow);
+	cqspi_phy_reset_setting(&rxhigh);
+	cqspi_phy_reset_setting(&second);
+
+	rxlow.read_delay = first.read_delay + 1;
+	if (rxlow.read_delay > CQSPI_PHY_MAX_RD)
+		goto compare;
+
+	ret = cqspi_find_rx_low_sdr(f_pdata, mem, &rxlow);
+	if (ret)
+		goto compare;
+
+	rxhigh.read_delay = rxlow.read_delay;
+	ret = cqspi_find_rx_high_sdr(f_pdata, mem, &rxhigh, rxlow.rx);
+	if (ret)
+		goto compare;
+
+	window2 = rxhigh.rx - rxlow.rx;
+	second.rx = rxlow.rx + (window2 / 2);
+	second.read_delay = rxlow.read_delay;
+
+	dev_dbg(dev, "Second tuning point: RX: %d TX: %d RD: %d\n", second.rx,
+		second.tx, second.read_delay);
+	ret = cqspi_phy_apply_setting(f_pdata, &second);
+	if (!ret)
+		ret = cqspi_phy_check_pattern(f_pdata, mem);
+
+	if (ret || second.read_delay > CQSPI_PHY_MAX_RD)
+		window2 = 0;
+
+compare:
+	cqspi_phy_reset_setting(&final);
+	if (window2 > window1) {
+		final.rx = second.rx;
+		final.read_delay = second.read_delay;
+	} else {
+		final.rx = first.rx;
+		final.read_delay = first.read_delay;
+	}
+
+	dev_dbg(dev, "Final tuning point: RX: %d TX: %d RD: %d\n", final.rx,
+		final.tx, final.read_delay);
+	ret = cqspi_phy_apply_setting(f_pdata, &final);
+	if (!ret)
+		ret = cqspi_phy_check_pattern(f_pdata, mem);
+
+	if (ret) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	f_pdata->phy_setting.read_delay = final.read_delay;
+	f_pdata->phy_setting.rx = final.rx;
+	f_pdata->phy_setting.tx = final.tx;
+
+out:
+	if (ret)
+		f_pdata->use_phy = false;
+
+	return ret;
+}
+
 static void cqspi_readdata_capture(struct cqspi_st *cqspi,
 				   const bool bypass,
 				   const unsigned int delay)
@@ -1366,6 +1702,83 @@ static void cqspi_config_baudrate_div(struct cqspi_st *cqspi)
 	writel(reg, reg_base + CQSPI_REG_CONFIG);
 }
 
+static void cqspi_phy_set_dll_master(struct cqspi_st *cqspi)
+{
+	void __iomem *reg_base = cqspi->iobase;
+	unsigned int reg;
+
+	reg = readl(reg_base + CQSPI_REG_PHY_DLL_MASTER);
+	reg &= ~((CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_LEN
+		  << CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_LSB) |
+		 CQSPI_REG_PHY_DLL_MASTER_BYPASS |
+		 CQSPI_REG_PHY_DLL_MASTER_CYCLE);
+	reg |= ((CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_3
+		 << CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_LSB) |
+		CQSPI_REG_PHY_DLL_MASTER_CYCLE);
+
+	writel(reg, reg_base + CQSPI_REG_PHY_DLL_MASTER);
+}
+
+static void cqspi_phy_pre_config(struct cqspi_st *cqspi, const bool bypass,
+				 struct cqspi_flash_pdata *f_pdata)
+{
+	void __iomem *reg_base = cqspi->iobase;
+	unsigned int reg;
+	u8 dummy;
+
+	cqspi_readdata_capture(cqspi, bypass,
+			       f_pdata->phy_setting.read_delay);
+
+	reg = readl(reg_base + CQSPI_REG_CONFIG);
+	reg &= ~(CQSPI_REG_CONFIG_PHY_EN | CQSPI_REG_CONFIG_PHY_PIPELINE);
+	reg |= CQSPI_REG_CONFIG_PHY_EN;
+	writel(reg, reg_base + CQSPI_REG_CONFIG);
+
+	reg = readl(reg_base + CQSPI_REG_RD_INSTR);
+	dummy = FIELD_GET(CQSPI_REG_RD_INSTR_DUMMY_MASK
+				  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+			  reg);
+	dummy--;
+	reg &= ~(CQSPI_REG_RD_INSTR_DUMMY_MASK << CQSPI_REG_RD_INSTR_DUMMY_LSB);
+	reg |= FIELD_PREP(CQSPI_REG_RD_INSTR_DUMMY_MASK
+				  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+			  dummy);
+	writel(reg, reg_base + CQSPI_REG_RD_INSTR);
+
+	cqspi_phy_set_dll_master(cqspi);
+}
+
+static void cqspi_phy_post_config(struct cqspi_st *cqspi,
+				  const unsigned int delay)
+{
+	void __iomem *reg_base = cqspi->iobase;
+	unsigned int reg;
+	u8 dummy;
+
+	reg = readl(reg_base + CQSPI_REG_READCAPTURE);
+	reg &= ~(CQSPI_REG_READCAPTURE_DELAY_MASK
+		 << CQSPI_REG_READCAPTURE_DELAY_LSB);
+
+	reg |= (delay & CQSPI_REG_READCAPTURE_DELAY_MASK)
+	       << CQSPI_REG_READCAPTURE_DELAY_LSB;
+	writel(reg, reg_base + CQSPI_REG_READCAPTURE);
+
+	reg = readl(reg_base + CQSPI_REG_CONFIG);
+	reg &= ~(CQSPI_REG_CONFIG_PHY_EN | CQSPI_REG_CONFIG_PHY_PIPELINE);
+	writel(reg, reg_base + CQSPI_REG_CONFIG);
+
+	reg = readl(reg_base + CQSPI_REG_RD_INSTR);
+	dummy = FIELD_GET(CQSPI_REG_RD_INSTR_DUMMY_MASK
+				  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+			  reg);
+	dummy++;
+	reg &= ~(CQSPI_REG_RD_INSTR_DUMMY_MASK << CQSPI_REG_RD_INSTR_DUMMY_LSB);
+	reg |= FIELD_PREP(CQSPI_REG_RD_INSTR_DUMMY_MASK
+				  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+			  dummy);
+	writel(reg, reg_base + CQSPI_REG_RD_INSTR);
+}
+
 static void cqspi_configure(struct cqspi_flash_pdata *f_pdata,
 			    unsigned long sclk)
 {
@@ -1724,6 +2137,39 @@ static bool cqspi_supports_mem_op(struct spi_mem *mem,
 	return spi_mem_default_supports_op(mem, op);
 }
 
+static int cqspi_mem_execute_tuning(struct spi_mem *mem,
+				    const struct spi_mem_op *op)
+{
+	struct cqspi_st *cqspi =
+		spi_controller_get_devdata(mem->spi->controller);
+	struct cqspi_flash_pdata *f_pdata;
+	struct device *dev = &cqspi->pdev->dev;
+	int ret;
+
+	f_pdata = &cqspi->f_pdata[spi_get_chipselect(mem->spi, 0)];
+	f_pdata->phy_read_op = *op;
+
+	if (!cqspi_phy_op_eligible_sdr(f_pdata, op)) {
+		dev_warn(dev,
+			 "Given read_op not eligible. Skipping PHY Tuning.\n");
+		return -EOPNOTSUPP;
+	}
+
+	ret = cqspi_phy_check_pattern(f_pdata, mem);
+	if (ret) {
+		dev_warn(dev, "Pattern not found. Skipping PHY Tuning.\n");
+		return -EINVAL;
+	}
+
+	cqspi_phy_pre_config(cqspi, true, f_pdata);
+	ret = cqspi_phy_tuning_sdr(f_pdata, mem);
+	if (ret)
+		dev_info(&cqspi->pdev->dev, "PHY Tuning failed: %d\n", ret);
+
+	cqspi_phy_post_config(cqspi, f_pdata->read_delay);
+	return ret;
+}
+
 static int cqspi_of_get_flash_pdata(struct platform_device *pdev,
 				    struct cqspi_flash_pdata *f_pdata,
 				    struct device_node *np)
@@ -1898,6 +2344,7 @@ static const struct spi_controller_mem_ops cqspi_mem_ops = {
 	.exec_op = cqspi_exec_mem_op,
 	.get_name = cqspi_get_name,
 	.supports_op = cqspi_supports_mem_op,
+	.execute_tuning = cqspi_mem_execute_tuning,
 };
 
 static const struct spi_controller_mem_caps cqspi_mem_caps = {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [RFC PATCH 10/10] spi: cadence-quadspi: Define cqspi_get_tuning_params()
  2025-08-11 19:32 [RFC PATCH 00/10] SPINAND PHY Tuning Series Santhosh Kumar K
                   ` (8 preceding siblings ...)
  2025-08-11 19:32 ` [RFC PATCH 09/10] spi: cadence-quadspi: Implement PHY for higher frequencies in SDR mode Santhosh Kumar K
@ 2025-08-11 19:32 ` Santhosh Kumar K
  9 siblings, 0 replies; 16+ messages in thread
From: Santhosh Kumar K @ 2025-08-11 19:32 UTC (permalink / raw)
  To: miquel.raynal, richard, vigneshr, broonie, tudor.ambarus,
	pratyush, mwalle, p-mantena
  Cc: linux-spi, linux-mtd, linux-kernel, s-k6, a-dutta, u-kumar1,
	praneeth

Define cqspi_get_tuning_params() to extract information about the PHY
tuning pattern and it's size from controller.

Signed-off-by: Santhosh Kumar K <s-k6@ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index 1626cb9a9700..c9c4341d3275 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -2170,6 +2170,14 @@ static int cqspi_mem_execute_tuning(struct spi_mem *mem,
 	return ret;
 }
 
+static int cqspi_get_tuning_params(struct spi_mem *mem,
+				   struct spi_mem_tuning_params *tuning_params)
+{
+	tuning_params->pattern_ptr = phy_tuning_pattern;
+	tuning_params->pattern_size = sizeof(phy_tuning_pattern);
+	return 0;
+}
+
 static int cqspi_of_get_flash_pdata(struct platform_device *pdev,
 				    struct cqspi_flash_pdata *f_pdata,
 				    struct device_node *np)
@@ -2345,6 +2353,7 @@ static const struct spi_controller_mem_ops cqspi_mem_ops = {
 	.get_name = cqspi_get_name,
 	.supports_op = cqspi_supports_mem_op,
 	.execute_tuning = cqspi_mem_execute_tuning,
+	.get_tuning_params = cqspi_get_tuning_params,
 };
 
 static const struct spi_controller_mem_caps cqspi_mem_caps = {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 01/10] spi: spi-mem: Introduce support for tuning controller
  2025-08-11 19:32 ` [RFC PATCH 01/10] spi: spi-mem: Introduce support for tuning controller Santhosh Kumar K
@ 2025-08-13 20:26   ` Mark Brown
  2025-08-14 11:34     ` Santhosh Kumar K
  2025-08-24 17:02     ` Miquel Raynal
  0 siblings, 2 replies; 16+ messages in thread
From: Mark Brown @ 2025-08-13 20:26 UTC (permalink / raw)
  To: Santhosh Kumar K
  Cc: miquel.raynal, richard, vigneshr, tudor.ambarus, pratyush, mwalle,
	p-mantena, linux-spi, linux-mtd, linux-kernel, a-dutta, u-kumar1,
	praneeth

[-- Attachment #1: Type: text/plain, Size: 880 bytes --]

On Tue, Aug 12, 2025 at 01:02:10AM +0530, Santhosh Kumar K wrote:
> From: Pratyush Yadav <pratyush@kernel.org>
> 
> Some controllers like the Cadence OSPI controller need to perform a
> tuning sequence to operate at high data rates. Tuning is needs to happen
> once the device is switched to appropriate mode (say 8S-8S-8S or
> 8D-8D-8D). Add a hook that spi-mem client devices can call in order to tune
> the controller to operate in a given mode and data rate.
> 
> This is somewhat similar to eMMC/SD tuning for higher speed modes like
> HS200, but there isn't a standard specification around the same though.

Should we have something that blocks these tuning required modes without
the appropriate tuning, and/or allows discovery of which modes require
this tuning?  This all feels very landmineish - client drivers just have
to know when tuning is required.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 01/10] spi: spi-mem: Introduce support for tuning controller
  2025-08-13 20:26   ` Mark Brown
@ 2025-08-14 11:34     ` Santhosh Kumar K
  2025-08-14 12:34       ` Mark Brown
  2025-08-24 17:02     ` Miquel Raynal
  1 sibling, 1 reply; 16+ messages in thread
From: Santhosh Kumar K @ 2025-08-14 11:34 UTC (permalink / raw)
  To: Mark Brown
  Cc: miquel.raynal, richard, vigneshr, tudor.ambarus, pratyush, mwalle,
	p-mantena, linux-spi, linux-mtd, linux-kernel, a-dutta, u-kumar1,
	praneeth, s-k6

Hello Mark,

On 14/08/25 01:56, Mark Brown wrote:
> On Tue, Aug 12, 2025 at 01:02:10AM +0530, Santhosh Kumar K wrote:
>> From: Pratyush Yadav <pratyush@kernel.org>
>>
>> Some controllers like the Cadence OSPI controller need to perform a
>> tuning sequence to operate at high data rates. Tuning is needs to happen
>> once the device is switched to appropriate mode (say 8S-8S-8S or
>> 8D-8D-8D). Add a hook that spi-mem client devices can call in order to tune
>> the controller to operate in a given mode and data rate.
>>
>> This is somewhat similar to eMMC/SD tuning for higher speed modes like
>> HS200, but there isn't a standard specification around the same though.
> 
> Should we have something that blocks these tuning required modes without
> the appropriate tuning, and/or allows discovery of which modes require
> this tuning?  This all feels very landmineish - client drivers just have
> to know when tuning is required.

The flash's maximum operating frequency determines whether PHY tuning is 
required, as we need tuning in case of Cadence controller for 
frequencies over 50 MHz.

And we do check for this condition - see Patch 07/10,
cqspi_phy_op_eligible_sdr(), which currently verifies the flash 
frequency against 166 MHz. This logic can be improved by implementing 
both min and max frequency checks, will update in the following version.

Thanks,
Santhosh.


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 01/10] spi: spi-mem: Introduce support for tuning controller
  2025-08-14 11:34     ` Santhosh Kumar K
@ 2025-08-14 12:34       ` Mark Brown
  2025-08-22  6:05         ` Santhosh Kumar K
  0 siblings, 1 reply; 16+ messages in thread
From: Mark Brown @ 2025-08-14 12:34 UTC (permalink / raw)
  To: Santhosh Kumar K
  Cc: miquel.raynal, richard, vigneshr, tudor.ambarus, pratyush, mwalle,
	p-mantena, linux-spi, linux-mtd, linux-kernel, a-dutta, u-kumar1,
	praneeth

[-- Attachment #1: Type: text/plain, Size: 1217 bytes --]

On Thu, Aug 14, 2025 at 05:04:33PM +0530, Santhosh Kumar K wrote:
> On 14/08/25 01:56, Mark Brown wrote:

> > Should we have something that blocks these tuning required modes without
> > the appropriate tuning, and/or allows discovery of which modes require
> > this tuning?  This all feels very landmineish - client drivers just have
> > to know when tuning is required.

> The flash's maximum operating frequency determines whether PHY tuning is
> required, as we need tuning in case of Cadence controller for frequencies
> over 50 MHz.

That's entirely specific to the Candence controller from the sounds of
it, that makes it hard to write a client driver if you need to know
exactly what the controller you're dealing with is and what it's
requirements are.

> And we do check for this condition - see Patch 07/10,
> cqspi_phy_op_eligible_sdr(), which currently verifies the flash frequency
> against 166 MHz. This logic can be improved by implementing both min and max
> frequency checks, will update in the following version.

I can't actually tell how that verifies if the tuning has been done
appropriately TBH, at least not without more effort than I'd care to
(and the tuning only gets added in patch 10?).

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 01/10] spi: spi-mem: Introduce support for tuning controller
  2025-08-14 12:34       ` Mark Brown
@ 2025-08-22  6:05         ` Santhosh Kumar K
  0 siblings, 0 replies; 16+ messages in thread
From: Santhosh Kumar K @ 2025-08-22  6:05 UTC (permalink / raw)
  To: Mark Brown
  Cc: miquel.raynal, richard, vigneshr, tudor.ambarus, pratyush, mwalle,
	p-mantena, linux-spi, linux-mtd, linux-kernel, a-dutta, u-kumar1,
	praneeth, s-k6



On 14/08/25 18:04, Mark Brown wrote:
> On Thu, Aug 14, 2025 at 05:04:33PM +0530, Santhosh Kumar K wrote:
>> On 14/08/25 01:56, Mark Brown wrote:
> 
>>> Should we have something that blocks these tuning required modes without
>>> the appropriate tuning, and/or allows discovery of which modes require
>>> this tuning?  This all feels very landmineish - client drivers just have
>>> to know when tuning is required.
> 
>> The flash's maximum operating frequency determines whether PHY tuning is
>> required, as we need tuning in case of Cadence controller for frequencies
>> over 50 MHz.
> 
> That's entirely specific to the Candence controller from the sounds of
> it, that makes it hard to write a client driver if you need to know
> exactly what the controller you're dealing with is and what it's
> requirements are.

PHY tuning is not very specific to the Cadence controller; this has been 
added for other controllers as well. [1] - [3]

spi_mem simply verifies the execute_tuning hook within the controller's 
mem_ops and invokes it if it exists, and the tuning implementation is 
entirely controller-dependent - ranging from straightforward parameter 
configuration of PHY registers to advanced tuning algorithms such as the 
one implemented in this tuning series.

Currently, spi_mem_execute_tuning() is called by default from flash. In 
the future, this could be improved by asking the controller if tuning is 
actually needed (considering different factors such as frequency), 
similar to *_get_tuning_params implementation. Let me know your opinion 
in this.

The get_tuning_params and execute_tuning hooks in spi_mem can also be 
utilized by any non-MTD spi-mem users.

> 
>> And we do check for this condition - see Patch 07/10,
>> cqspi_phy_op_eligible_sdr(), which currently verifies the flash frequency
>> against 166 MHz. This logic can be improved by implementing both min and max
>> frequency checks, will update in the following version.
> 
> I can't actually tell how that verifies if the tuning has been done
> appropriately TBH, at least not without more effort than I'd care to

The *_execute_tuning function takes the read_op as an argument from 
flash, and considering flash continues to utilize the same read_op and 
frequency, it should make sure the tuning is appropriately completed. In 
the Cadence controller, the tuning process is validated by performing a 
read-back of a pre-defined tuning pattern using the read_op provided by 
flash.

> (and the tuning only gets added in patch 10?).

Patches 7 and 8 add PHY read/write support, and patch 9 adds tuning. 
These three patches could be squashed into one, but kept them separate 
to make it more granular for the reviewers.

[1] https://lore.kernel.org/linux-spi/20220509175616.1089346-1-clg@kaod.org/
[2] https://lore.kernel.org/all/20230322090451.3179431-2-haibo.chen@nxp.com/
[3] 
https://lore.kernel.org/all/20241128174316.3209354-1-csokas.bence@prolan.hu/

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH 01/10] spi: spi-mem: Introduce support for tuning controller
  2025-08-13 20:26   ` Mark Brown
  2025-08-14 11:34     ` Santhosh Kumar K
@ 2025-08-24 17:02     ` Miquel Raynal
  1 sibling, 0 replies; 16+ messages in thread
From: Miquel Raynal @ 2025-08-24 17:02 UTC (permalink / raw)
  To: Mark Brown
  Cc: Santhosh Kumar K, richard, vigneshr, tudor.ambarus, pratyush,
	mwalle, p-mantena, linux-spi, linux-mtd, linux-kernel, a-dutta,
	u-kumar1, praneeth

Hello,

On 13/08/2025 at 21:26:06 +01, Mark Brown <broonie@kernel.org> wrote:

> On Tue, Aug 12, 2025 at 01:02:10AM +0530, Santhosh Kumar K wrote:
>> From: Pratyush Yadav <pratyush@kernel.org>
>> 
>> Some controllers like the Cadence OSPI controller need to perform a
>> tuning sequence to operate at high data rates. Tuning is needs to happen
>> once the device is switched to appropriate mode (say 8S-8S-8S or
>> 8D-8D-8D). Add a hook that spi-mem client devices can call in order to tune
>> the controller to operate in a given mode and data rate.
>> 
>> This is somewhat similar to eMMC/SD tuning for higher speed modes like
>> HS200, but there isn't a standard specification around the same though.
>
> Should we have something that blocks these tuning required modes without
> the appropriate tuning, and/or allows discovery of which modes require
> this tuning?  This all feels very landmineish - client drivers just have
> to know when tuning is required.

The maximum bus frequency will tell whether tuning is relevant or not I
guess.

In the case of the Cadence controller, the bus speed is key to determine
whether calibration should happen or not because when PHY calibration is
enabled, the SPI bus frequency is equal to the controller clock rate
(pre-scalers are bypassed).

So the criteria for enabling calibration is:

   max SPI bus freq >=  min controller clock rate

Thanks,
Miquèl

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2025-08-24 17:03 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-11 19:32 [RFC PATCH 00/10] SPINAND PHY Tuning Series Santhosh Kumar K
2025-08-11 19:32 ` [RFC PATCH 01/10] spi: spi-mem: Introduce support for tuning controller Santhosh Kumar K
2025-08-13 20:26   ` Mark Brown
2025-08-14 11:34     ` Santhosh Kumar K
2025-08-14 12:34       ` Mark Brown
2025-08-22  6:05         ` Santhosh Kumar K
2025-08-24 17:02     ` Miquel Raynal
2025-08-11 19:32 ` [RFC PATCH 02/10] spi: spi-mem: Define spi_mem_tuning_params and spi_mem_get_tuning_params() Santhosh Kumar K
2025-08-11 19:32 ` [RFC PATCH 03/10] mtd: nand: spi: Introduce _execute_tuning for mtd devices Santhosh Kumar K
2025-08-11 19:32 ` [RFC PATCH 04/10] mtd: mtdcore: Call mtd_execute_tuning during mtd_register Santhosh Kumar K
2025-08-11 19:32 ` [RFC PATCH 05/10] spi: cadence-quadspi: Move cqspi_readdata_capture() above all operations Santhosh Kumar K
2025-08-11 19:32 ` [RFC PATCH 06/10] spi: cadence-quadspi: Use BIT() macro for CQSPI_REG_READCAPTURE_BYPASS Santhosh Kumar K
2025-08-11 19:32 ` [RFC PATCH 07/10] spi: cadence-quadspi: Enable PHY for aligned DAC reads Santhosh Kumar K
2025-08-11 19:32 ` [RFC PATCH 08/10] spi: cadence-quadspi: Enable PHY for data writes Santhosh Kumar K
2025-08-11 19:32 ` [RFC PATCH 09/10] spi: cadence-quadspi: Implement PHY for higher frequencies in SDR mode Santhosh Kumar K
2025-08-11 19:32 ` [RFC PATCH 10/10] spi: cadence-quadspi: Define cqspi_get_tuning_params() Santhosh Kumar K

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).