[PATCH v10 00/19] spi: cadence-quadspi: add PHY tuning support
Boyapally, Srikanth
Srikanth.Boyapally at amd.com
Thu Oct 15 10:47:23 PDT 2026
AMD General
Hi Santhosh,
I tested this series on SPI-NOR flash devices on the AMD Versal
Net platform by integrating AMD SoC-specific PHY support on top of
your patch series.
Read throughput:
DTR-PHY (200 MHz): 271 MB/s
SDR non-PHY (20 MHz): 19.60 MB/s
To validate functionality and robustness, I completed JFFS2 and
raw MTD erase/write/read-back testing with random data across
10/10 successful runs. I also verified odd length reads, which
consistently matched the corresponding aligned reference reads.
For the SPI, spi-mem, SPI NOR, and cadence-quadspi patches,
please feel free to add:
Tested-by: Srikanth Boyapally <srikanth.boyapally at amd.com>
Thank you,
Srikanth
> -----Original Message-----
> From: Santhosh Kumar K <s-k6 at ti.com>
> Sent: Tuesday, September 29, 2026 12:05 PM
> To: broonie at kernel.org; robh at kernel.org; krzk+dt at kernel.org;
> conor+dt at kernel.org; miquel.raynal at bootlin.com; richard at nod.at;
> vigneshr at ti.com; pratyush at kernel.org; mwalle at kernel.org;
> takahiro.kuwano at infineon.com
> Cc: linux-spi at vger.kernel.org; devicetree at vger.kernel.org; linux-
> kernel at vger.kernel.org; linux-mtd at lists.infradead.org; praneeth at ti.com;
> sebin.francis at ti.com; u-kumar1 at ti.com; a-dutta at ti.com; s-k6 at ti.com
> Subject: [PATCH v10 00/19] spi: cadence-quadspi: add PHY tuning support
>
> Caution: This message originated from an External Source. Use proper caution
> when opening attachments, clicking links, or responding.
>
>
> This series implements PHY tuning support for the Cadence QSPI controller to
> enable reliable high-speed operations. Without PHY tuning, controllers use
> conservative timing that limits performance. PHY tuning calibrates RX/TX delay
> lines to find optimal data capture timing windows, enabling operation up to
> the controller's maximum frequency.
>
> Background:
> High-speed SPI memory controllers require precise timing calibration for
> reliable operation. At higher frequencies, board-to-board variations make fixed
> timing parameters inadequate. The Cadence QSPI controller includes a PHY
> interface with programmable delay lines (0-127 taps) for RX and TX paths, but
> these require runtime calibration to find the valid timing window.
>
> Approach:
> Add SDR/DDR PHY tuning algorithms for the Cadence controller:
>
> SDR Mode Tuning (1D search):
> - Searches for two consecutive valid RX delay windows
> - Selects the larger window and uses its midpoint for maximum margin
> - TX delay fixed at maximum (127) as it's less critical in SDR
>
> DDR Mode Tuning (2D search):
> - Finds RX boundaries (rxlow/rxhigh) using TX window sweeps
> - Finds TX boundaries (txlow/txhigh) at fixed RX positions
> - Defines valid region corners and detects gaps via binary search
> - Applies temperature compensation for optimal point selection
> - Handles single or dual passing regions with different strategies
>
> Patch description:
> Infrastructure (1-5):
> - Patch 1: Add spi-max-post-config-frequency-hz to describe maximum
> frequency achievable post controller configuration
> - Patch 2: Add spi-phy-pattern-partition phandle for
> NOR flash PHY tuning pattern location
> - Patch 3: Parse spi-max-post-config-frequency-hz in spi.c; adds
> spi_device.post_config_max_speed_hz (0 when not set
> keeping all existing DT fully compatible)
> - Patch 4: Extend spi_mem_adjust_op_freq() with a bypass: if
> op->max_freq equals post_config_max_speed_hz, return
> immediately leaving op->max_freq unchanged. All other
> ops are capped to max_speed_hz. Also require per_op_freq
> controller capability for post-config ops in
> spi_mem_default_supports_op()
> - Patch 5: Add execute_tuning callback to spi_controller_mem_ops and
> spi_mem_execute_tuning() wrapper in SPI-MEM core;
> serialises access via spi_mem_access_start/end and
> rejects GPIO chip selects
>
> Cadence QSPI Implementation (6-14):
> - Patch 6: Move cqspi_readdata_capture() earlier (preparatory)
> - Patch 7: Add DQS bit to cqspi_readdata_capture() (preparatory)
> - Patch 8: Add complete PHY tuning support: DLL management, pattern
> verification (NOR via spi-phy-pattern-partition phandle,
> NAND via write-to-cache), SDR 1D and DDR 2D search
> algorithms with temperature compensation, AM654-specific
> execute_tuning entry point
> - Patch 9: Reject 2-byte-address DDR operations via a new
> CQSPI_NO_PHY_TUNING_WITH_ODDR_2B_ADDR quirk flag to work
> around AM654 OSPI erratum i2383
> - Patch 10: Refactor direct read path for PHY support (preparatory)
> - Patch 11: Enable PHY for direct reads at the calibrated frequency
> when both transfer boundaries are 16-byte aligned,
> falling back to non-PHY DMA otherwise; also reprograms
> DLL on chip-select switch when two devices have
> different calibrated settings
> - Patch 12: Enable PHY for indirect writes of at least
> CQSPI_PHY_MIN_INDIRECT_WRITE_LEN bytes
> - Patch 13: Reprogram per-device CS timing on any chip-select switch
> - Patch 14: Reprogram the PHY DLL (master config and RX/TX taps) on
> runtime resume, since the DLL loses its lock when the
> OSPI controller is power-gated
>
> MTD core (15-19):
> - Patch 15: Extract spinand_select_op_variant() into a shared helper
> spinand_op_find_best_variant() with a skip_mask
> - Patch 16: Negotiate optimal controller operating point before dirmap
> creation; iterate all read variants in performance order
> across ODTR and SSDR interfaces; re-pair cont_read_cache after
> PHY tuning via protocol-shape matching
> - Patch 17: Extract spi_nor_spimem_get_read_op() helper (preparatory)
> - Patch 18: Execute controller optimization in spi_nor_probe() before
> creating dirmaps
> - Patch 19: Reset max_read_op frequency for security register reads
>
> Testing:
> This series was tested on TI's
> AM62Ax SK with OSPI NAND flash and
> AM62Px SK with OSPI NOR flash:
>
> Read throughput:
> |----------------------------------------|
> | | non-PHY | PHY |
> |----------------------------------------|
> | OSPI NOR (8D) | 37.5 MB/s | 218 MB/s |
> |----------------------------------------|
> | OSPI NAND (8S) | 9.2 MB/s | 35.6 MB/s |
> |----------------------------------------|
>
> Write throughput:
> |----------------------------------------|
> | | non-PHY | PHY |
> |----------------------------------------|
> | OSPI NAND (8S) | 6 MB/s | 9.2 MB/s |
> |----------------------------------------|
>
> Test log:
> https://gist.github.com/santhosh21/e1b5c7cc5cad4acb94b67e826d971ab9
> Repo: https://github.com/santhosh21/linux/commits/phy_tuning_v10
>
> Changes in v10:
> - Gate the frequency spi_nor_spimem_get_read_op() propagates on
> matching
> protocol shape, and add a patch resetting max_read_op.max_freq around
> the security-register read override
> - Split cqspi_tune_phy() into cqspi_tune_phy_read()/cqspi_tune_phy_write()
> so PHY_PIPELINE is never set on writes, per AM65x erratum i2189
> - Fix spinand_try_ranked_variant() to only clear, never set
> cont_read_possible on ranked-variant fallback
> - Fix other, smaller correctness issues raised by sashiko-bot
> - Collect tag from Miquel
> - Link to v9: https://lore.kernel.org/linux-spi/20260825171737.776052-1-s-
> k6 at ti.com/
>
> Changes in v9:
> - Fix off-by-one in DDR PHY TX search loops
> - Fix cqspi_tune_phy() to unconditionally execute the disable path
> and restore CQSPI_REG_READCAPTURE on enable error rollback
> - Fix other, smaller correctness issues raised by sashiko-bot
> - Link to v8: https://lore.kernel.org/linux-spi/20260821114822.4040338-1-
> s-k6 at ti.com/
>
> Changes in v8:
> - Re-pair cont_read_cache with the selected read_cache variant via
> protocol-shape matching after PHY tuning instead of unconditionally
> diabling continuous read
> - Rebase on v7.2
> - Collect tag from Miquel
> - Link to v7: https://lore.kernel.org/linux-spi/20260811183313.1550425-1-
> s-k6 at ti.com/
>
> Changes in v7:
> - Serialise spi_mem_execute_tuning() with spi_mem_access_start/end and
> reject GPIO chip-select configurations
> - Fix cqspi_phy_apply_setting() to also write the calibrated
> read_delay to CQSPI_REG_READCAPTURE, previously software-only
> - Use local op copies for pattern write/verify instead of mutating
> the caller's op templates in place
> - Fix the READCAPTURE bypass bit in cqspi_tune_phy() to match the
> value validated during calibration instead of being hardcoded
> - Enable PHY before starting the indirect-write state machine instead
> of after; the old order was a guaranteed deadlock
> - Add a new patch to reprogram the PHY DLL on runtime resume
> - Fix continuous-read and ODTR/SSDR template handling on ranked
> fallback in spinand_try_ranked_variant(), preventing data
> corruption after a fallback
> - Fix other, smaller correctness issues raised by sashiko-bot
> - Rebase on v7.2-rc7
> - Link to v6: https://lore.kernel.org/linux-spi/20260723110219.3180747-1-
> s-k6 at ti.com/
>
> Changes in v6:
> - Add maxItems to spi-max-post-config-frequency-hz property
> - Rebase on v7.2-rc4
> - Collect tag from Krzysztof
> - Link to v5: https://lore.kernel.org/linux-spi/20260717185116.2065505-1-
> s-k6 at ti.com/
>
> Changes in v5:
> - Rename spi-max-post-config-frequency to spi-max-post-config-frequency-
> hz
> per DT schema unit suffix convention
> - Rename CQSPI_NO_2BYTE_ADDR_PHY_DDR quirk to
> CQSPI_NO_PHY_TUNING_WITH_ODDR_2B_ADDR for clarity
> - Rename SPI NAND identifiers to remove PHY-specific terminology:
> spinand_configure_phy() -> spinand_optimize_controller()
> spinand_try_phy_ranked() -> spinand_try_ranked_variant()
> spinand_reset_max_ops() -> spinand_reset_max_freq_ops()
> spinand_op_find_best() -> spinand_op_find_best_variant()
> phy_read_variants (field) -> all_read_variants
> - Replace bool odtr parameter with enum spinand_bus_interface iface in
> spinand_op_find_best_variant() to preserve the existing enumeration
> - Replace cqspi_get_phy_pattern_offset() with per-flash probe-time
> phandle lookup in cqspi_of_get_flash_pdata(); fixes incorrect pattern
> offset selection when multiple flash devices share a controller
> - In cqspi_tune_phy(), reprogramme and resync the DLL when the active
> device's calibrated settings differ from hardware
> - Edit cqspi_phy_apply_setting() to update phy_setting.rx and
> phy_setting.tx alongside phy_setting.read_delay
> - Reprogram per-device CS timing (CQSPI_REG_DELAY and read-capture
> register)
> on any chip-select switch, not only on clock changes
> - Move post_config_max_speed_hz early-return to the top of
> spinand_optimize_controller(), before any state is modified
> - Remove dead NULL guards on SSDR op templates in Pass 2
> - Downgrade tuning-failure log messages from dev_warn to dev_dbg
> - Rebase on v7.2-rc3
> - Collect tags
> - Link to v4: https://lore.kernel.org/linux-spi/20260618073725.84733-1-s-
> k6 at ti.com/
>
> Changes in v4:
> - Add spi-max-post-config-frequency instead of extending spi-max-frequency
> to accept an optional second value
> - Replace spi_mem_apply_base_freq_cap() with spi_mem_adjust_op_freq()
> extension
> - For SPI NOR/NAND, execute PHY tuning before the dirmap creation
> - For SPI NAND, execute PHY tuning across all operation variants available,
> perform duration comparison, and select the best resulting variant
> by taking controller-specific restrictions into account
> - Move i2383 check from cqspi_supports_mem_op() to
> cqspi_am654_ospi_execute_tuning()
> - Rename cdns,phy-pattern-partition to spi-phy-pattern-partition,
> cqspi_phy_enable to cqspi_tune_phy and f_pdata->use_phy to
> use_tuned_phy
> - Remove redundant spi-max-frequency parsing in driver
> cqspi_of_get_flash_pdata()
> - Extract DMA refactoring into a preparatory patch
> - Rebase on v7.1
> - Collect tags from Miquel
> - Link to v3: https://lore.kernel.org/linux-spi/20260527175527.2247679-1-
> s-k6 at ti.com/
>
> Changes in v3:
> - Drop spi-has-dqs DT property; DQS is now enabled automatically when
> the selected read operation uses DDR signalling (dtr flags in the op)
> - Extend spi-max-frequency to accept an optional second value forming a
> [base-freq, max-freq] pair; the presence of two values signals PHY
> tuning intent and encodes both the conservative base speed and the
> calibration target in one property
> - Add base_speed_hz to struct spi_device (spi.c/spi.h) and parse the
> two-element array there; single-value DT is fully backward-compatible
> - Move frequency enforcement from the cadence driver to core: new
> spi_mem_apply_base_freq_cap() called from spi_mem_exec_op() replaces
> the per-driver cqspi_op_matches_tuned() and non_phy_clk_rate field
> - Propagate the tuned max_freq to dirmap op templates after
> execute_tuning() succeeds; store persistent op templates in
> spi_nor.max_read_op and spinand.{max_read,max_write}_op so the
> frequency writeback survives across the probe call
> - Replace NOR pattern partition lookup by name with a
> cdns,phy-pattern-partition DT phandle pointing directly to the
> partition node
> - Add CQSPI_NO_2BYTE_ADDR_PHY_DDR quirk and reject 2-byte-address
> DDR
> ops in cqspi_supports_mem_op() to work around AM654 erratum i2383
> - Remove RFC tag
> - Rebase on v7.1-rc5
> - Collect tags from Miquel
> - Link to v2: https://lore.kernel.org/linux-spi/20260113141617.1905039-1-
> s-k6 at ti.com/
>
> Changes in v2:
> - Restructure the .execute_tuning() call from spi-mem clients instead
> of mtdcore with best read_op and write_op (optional) passed
> - Add compatible-specific .execute_tuning() call which can be called by
> spi_mem_execute_tuning() if exists
> - Handle tuning requirement check by controller instead of spi-mem
> clients
> - Add support to write the phy_pattern to cache if relevant write_op
> is passed or get the partition offset which contains the phy_pattern
> - Add tuning algorithm for DDR mode
> - Add support for DQS
> - Restrict PHY frequency to tuned operations
> - Link to v1: https://lore.kernel.org/linux-spi/20250811193219.731851-1-s-
> k6 at ti.com/
>
> Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
>
> Pratyush Yadav (1):
> mtd: spi-nor: extract read op template construction into helper
>
> Santhosh Kumar K (18):
> spi: dt-bindings: add spi-max-post-config-frequency-hz property
> spi: dt-bindings: add spi-phy-pattern-partition property
> spi: parse spi-max-post-config-frequency-hz into
> post_config_max_speed_hz
> spi: spi-mem: teach spi_mem_adjust_op_freq() about post-config ops
> spi: spi-mem: add execute_tuning callback and spi_mem_execute_tuning()
> spi: cadence-quadspi: move cqspi_readdata_capture earlier
> spi: cadence-quadspi: add DQS support to read data capture
> spi: cadence-quadspi: add PHY tuning support
> spi: cadence-quadspi: skip DDR PHY tuning for 2-byte-address ops
> (i2383)
> spi: cadence-quadspi: refactor direct read path for PHY support
> spi: cadence-quadspi: enable PHY for direct reads
> spi: cadence-quadspi: enable PHY for indirect writes
> spi: cadence-quadspi: reprogram CS timing on every chip-select switch
> spi: cadence-quadspi: reprogram PHY DLL on runtime resume
> mtd: spinand: extract variant ranking logic into
> spinand_op_find_best_variant()
> mtd: spinand: negotiate optimal controller operating point before
> dirmap creation
> mtd: spi-nor: run controller optimization before dirmap creation
> mtd: spi-nor: reset max_read_op frequency for security register reads
>
> .../bindings/spi/cdns,qspi-nor.yaml | 17 +
> .../bindings/spi/spi-peripheral-props.yaml | 13 +
> drivers/mtd/nand/spi/core.c | 328 ++-
> drivers/mtd/spi-nor/core.c | 94 +-
> drivers/mtd/spi-nor/otp.c | 5 +
> drivers/spi/spi-cadence-quadspi.c | 2275 ++++++++++++++++-
> drivers/spi/spi-mem.c | 55 +-
> drivers/spi/spi.c | 2 +
> include/linux/mtd/spi-nor.h | 4 +
> include/linux/mtd/spinand.h | 15 +
> include/linux/spi/spi-mem.h | 14 +
> include/linux/spi/spi.h | 3 +
> 12 files changed, 2671 insertions(+), 154 deletions(-)
>
> --
> 2.34.1
More information about the linux-mtd
mailing list