From: sashiko-bot@kernel.org
To: "Santhosh Kumar K" <s-k6@ti.com>
Cc: conor+dt@kernel.org, "Richard Weinberger" <richard@nod.at>,
robh@kernel.org, "Miquel Raynal" <miquel.raynal@bootlin.com>,
"Pratyush Yadav" <pratyush@kernel.org>,
"Michael Walle" <mwalle@kernel.org>,
devicetree@vger.kernel.org,
"Vignesh Raghavendra" <vigneshr@ti.com>,
"Takahiro Kuwano" <takahiro.kuwano@infineon.com>
Subject: Re: [PATCH v6 08/17] spi: cadence-quadspi: add PHY tuning support
Date: Thu, 23 Jul 2026 11:33:56 +0000 [thread overview]
Message-ID: <20260723113356.B5BB91F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260723110219.3180747-9-s-k6@ti.com>
Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [High] Hardware registers configured for PHY tuning are unconditionally clobbered by the driver's generic transfer routines.
- [High] Uninitialized variable `ret` is returned on the error path when skipping tuning, and offset 0 is incorrectly rejected.
- [High] The PHY tuning loops sweep over `read_delay` but fail to apply it to the hardware, rendering the delay search ineffective.
- [High] Unsafe `u8` arithmetic in the dual-region DDR tuning path can underflow, configuring out-of-bounds delays.
- [High] The tuning procedure mutates global controller registers and runs over a long duration without holding the SPI bus lock.
- [High] The tuning procedure directly mutates the caller's operation templates (`read_op` and `write_op`), corrupting them for subsequent uses.
- [Medium] CQSPI_REG_PHY_DLL_MASTER_INIT_DELAY is not cleared before ORing the new value, potentially accumulating stale bits.
- [Medium] Missing error check after the initial `rxlow` search in SDR tuning causes anomalous out-of-bounds execution.
- [Low] The field `phy_write_op` is populated but completely unused.
--
--- Patch [8]: [PATCH v6 08/17] spi: cadence-quadspi: add PHY tuning support ---
commit 72b444b4b37e26e6d981401c000139a0fd3aac27
Author: Santhosh Kumar K <s-k6@ti.com>
spi: cadence-quadspi: add PHY tuning support
This commit adds an execute_tuning callback for delay-line calibration on
the Cadence QSPI controller. It performs a sweep of RX and TX delays to find
an optimal operating window for DDR and SDR protocols.
> diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
> index 046fa5d69b0f7..22a5c88bbd8dc 100644
> --- a/drivers/spi/spi-cadence-quadspi.c
> +++ b/drivers/spi/spi-cadence-quadspi.c
[ ... ]
> +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,
> + struct cqspi_flash_pdata *f_pdata,
> + const bool bypass)
> +{
> + void __iomem *reg_base = cqspi->iobase;
> + unsigned int reg;
> + u8 dummy;
> +
> + cqspi_readdata_capture(cqspi, bypass, f_pdata->use_dqs,
> + 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);
[Severity: High]
Will this register state survive the tuning reads? When
cqspi_phy_check_pattern() invokes spi_mem_exec_op(), the driver eventually
calls cqspi_configure() and other standard transfer routines. These routines
typically recalculate and overwrite CQSPI_REG_RD_INSTR, which could destroy
this dummy cycle decrement before the test pattern is actually read.
Furthermore, cqspi_configure() unconditionally invokes
cqspi_readdata_capture() during clock changes, which might wipe out the EDGE
bit and DQS settings.
> +
> + cqspi_phy_set_dll_master(cqspi);
> +}
[ ... ]
> +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)
> + return 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);
[Severity: Medium]
Is it safe to bitwise OR the initial delay value without clearing the
existing bits first? If a bootloader or previous initialization left
non-zero bits in this register field, they will remain and intermix with
the new value, potentially leading to an unpredictable DLL master delay.
[ ... ]
> +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;
> + int ret;
> +
> + reg = readl(cqspi->iobase + CQSPI_REG_READCAPTURE);
> + reg |= BIT(CQSPI_REG_READCAPTURE_EDGE_LSB);
> + writel(reg, cqspi->iobase + CQSPI_REG_READCAPTURE);
> +
> + cqspi_set_dll(cqspi->iobase, phy->rx, phy->tx);
> +
> + ret = cqspi_resync_dll(cqspi);
> + if (ret)
> + return ret;
> +
> + f_pdata->phy_setting.rx = phy->rx;
> + f_pdata->phy_setting.tx = phy->tx;
> + f_pdata->phy_setting.read_delay = phy->read_delay;
> + return 0;
> +}
[Severity: High]
Does this function need to write the read_delay to the hardware? The tuning
loops sweep over the read_delay parameter, but this only updates the
software state in f_pdata. Because the new read_delay is never written to
CQSPI_REG_READCAPTURE here, it seems the multidimensional tuning algorithm
might fail to test different read delays and run all tests at the initial
delay value.
[ ... ]
> +static int cqspi_phy_tuning_ddr(struct cqspi_flash_pdata *f_pdata,
> + struct spi_mem *mem)
> +{
[ ... ]
> + rxlow.tx = CQSPI_PHY_TX_LOOKUP_LOW_START;
> + do {
> + dev_dbg(dev, "Searching for Golden Primary rxlow on TX = %d\n",
> + rxlow.tx);
> + rxlow.read_delay = CQSPI_PHY_INIT_RD;
> + ret = cqspi_find_rx_low_ddr(f_pdata, mem, &rxlow);
[Severity: High]
Could concurrent transactions from other SPI devices corrupt the PHY state
during this loop? The SPI core acquires the bus lock per exec_op inside
cqspi_phy_check_pattern(), meaning the lock is released between iterations.
If another SPI device performs a transfer between iterations, it might run
with the PHY incorrectly enabled and its cqspi_configure() call could
overwrite the global CQSPI_REG_READCAPTURE register, breaking the tuning
procedure.
[ ... ]
> + /* Compare Manhattan distances: choose corner furthest from gap */
> + if ((abs(gaplow.tx - bottomleft.tx) +
> + abs(gaplow.rx - bottomleft.rx)) <
> + (abs(gaphigh.tx - topright.tx) +
> + abs(gaphigh.rx - topright.rx))) {
> + /* Topright further: Use Region 2, 16 taps inward */
> + searchpoint = topright;
> + searchpoint.tx -= 16;
> + searchpoint.rx -= (16 * (topright.rx - bottomleft.rx)) /
> + (topright.tx - bottomleft.tx);
[Severity: High]
Can this integer math underflow? Because searchpoint.tx is a u8, if the
valid region corner topright.tx is less than 16, this subtraction could wrap
around to a large value like 240. This seems different from the
single-region path which uses clamp() and int casts to prevent wraparound.
[ ... ]
> +static int cqspi_phy_tuning_sdr(struct cqspi_flash_pdata *f_pdata,
> + struct spi_mem *mem)
> +{
[ ... ]
> + 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);
> +
> + /* Find rxhigh: Decrement from RX=127 at same read_delay */
> +
> + rxhigh.read_delay = rxlow.read_delay;
> + ret = cqspi_find_rx_high_sdr(f_pdata, mem, &rxhigh, rxlow.rx);
[Severity: Medium]
Is a check for ret missing here after the while loop? If
cqspi_find_rx_low_sdr fails for all read delays, it exits the loop with
ret = -ENOENT. Without a check for ret, the code proceeds to use an
out-of-bounds read_delay and blindly invokes cqspi_find_rx_high_sdr().
[ ... ]
> +static int cqspi_write_pattern_to_cache(struct cqspi_flash_pdata *f_pdata,
> + struct spi_mem *mem,
> + struct spi_mem_op *write_op)
> +{
> + struct device *dev = &f_pdata->cqspi->pdev->dev;
> + int ret;
> +
> + write_op->data.nbytes = sizeof(phy_tuning_pattern);
> + write_op->data.buf.out = phy_tuning_pattern;
[Severity: High]
Does this overwrite the caller's operation template in place? Modifying
write_op->data.buf.out directly alters the pointer belonging to the caller
(the SPI core or flash layer). This might corrupt the template for
subsequent transfers, causing the PHY tuning pattern to be written instead
of real data.
> +
> + ret = spi_mem_exec_op(mem, write_op);
[ ... ]
> +static int cqspi_am654_ospi_execute_tuning(struct spi_mem *mem,
> + struct spi_mem_op *read_op,
> + struct spi_mem_op *write_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)];
> +
> + if (!mem->spi->post_config_max_speed_hz) {
> + dev_dbg(dev,
> + "No post-config frequency configured, skipping tuning\n");
> + return -EOPNOTSUPP;
> + }
> +
> + if (write_op) {
> + ret = cqspi_write_pattern_to_cache(f_pdata, mem, write_op);
> + if (ret) {
> + dev_warn(dev,
> + "failed to write pattern to cache: %d, skipping tuning\n",
> + ret);
> + goto out;
> + }
> +
> + f_pdata->phy_write_op = *write_op;
[Severity: Low]
Is f_pdata->phy_write_op ever read again? It appears to be populated here
but remains unused elsewhere in the driver.
> + } else {
> + if (!f_pdata->phy_pattern_partition_offset) {
> + dev_warn(dev, "pattern partition not found, skipping tuning\n");
> + goto out;
> + }
[Severity: High]
If the pattern partition offset is intentionally 0, will this falsely reject
it? Also, jumping to out leaves ret uninitialized. Could this result in
returning garbage stack memory to the SPI core?
> + read_op->addr.val = f_pdata->phy_pattern_partition_offset;
> + }
[Severity: High]
Does modifying read_op->addr.val in place permanently alter the caller's SPI
template? Similar to the write_op modification, this could cause data
corruption if the caller reuses this template for subsequent operations.
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723110219.3180747-1-s-k6@ti.com?part=8
next prev parent reply other threads:[~2026-07-23 11:33 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-23 11:02 [PATCH v6 00/17] spi: cadence-quadspi: add PHY tuning support Santhosh Kumar K
2026-07-23 11:02 ` [PATCH v6 01/17] spi: dt-bindings: add spi-max-post-config-frequency-hz property Santhosh Kumar K
2026-07-23 11:02 ` [PATCH v6 02/17] spi: dt-bindings: add spi-phy-pattern-partition property Santhosh Kumar K
2026-07-23 11:02 ` [PATCH v6 03/17] spi: parse spi-max-post-config-frequency-hz into post_config_max_speed_hz Santhosh Kumar K
2026-07-23 11:02 ` [PATCH v6 04/17] spi: spi-mem: teach spi_mem_adjust_op_freq() about post-config ops Santhosh Kumar K
2026-07-23 11:34 ` sashiko-bot
2026-07-23 11:02 ` [PATCH v6 05/17] spi: spi-mem: add execute_tuning callback and spi_mem_execute_tuning() Santhosh Kumar K
2026-07-23 11:30 ` sashiko-bot
2026-07-23 11:02 ` [PATCH v6 06/17] spi: cadence-quadspi: move cqspi_readdata_capture earlier Santhosh Kumar K
2026-07-23 11:02 ` [PATCH v6 07/17] spi: cadence-quadspi: add DQS support to read data capture Santhosh Kumar K
2026-07-23 11:28 ` sashiko-bot
2026-07-23 11:02 ` [PATCH v6 08/17] spi: cadence-quadspi: add PHY tuning support Santhosh Kumar K
2026-07-23 11:33 ` sashiko-bot [this message]
2026-07-23 11:02 ` [PATCH v6 09/17] spi: cadence-quadspi: skip DDR PHY tuning for 2-byte-address ops (i2383) Santhosh Kumar K
2026-07-23 11:48 ` sashiko-bot
2026-07-23 11:02 ` [PATCH v6 10/17] spi: cadence-quadspi: refactor direct read path for PHY support Santhosh Kumar K
2026-07-23 11:41 ` sashiko-bot
2026-07-23 11:02 ` [PATCH v6 11/17] spi: cadence-quadspi: enable PHY for direct reads Santhosh Kumar K
2026-07-23 11:48 ` sashiko-bot
2026-07-23 11:02 ` [PATCH v6 12/17] spi: cadence-quadspi: enable PHY for indirect writes Santhosh Kumar K
2026-07-23 11:44 ` sashiko-bot
2026-07-23 11:02 ` [PATCH v6 13/17] spi: cadence-quadspi: reprogram CS timing on every chip-select switch Santhosh Kumar K
2026-07-23 11:49 ` sashiko-bot
2026-07-23 11:02 ` [PATCH v6 14/17] mtd: spinand: extract variant ranking logic into spinand_op_find_best_variant() Santhosh Kumar K
2026-07-23 11:02 ` [PATCH v6 15/17] mtd: spinand: negotiate optimal controller operating point before dirmap creation Santhosh Kumar K
2026-07-23 12:02 ` sashiko-bot
2026-07-23 11:02 ` [PATCH v6 16/17] mtd: spi-nor: extract read op template construction into helper Santhosh Kumar K
2026-07-23 11:02 ` [PATCH v6 17/17] mtd: spi-nor: run controller optimization before dirmap creation Santhosh Kumar K
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260723113356.B5BB91F000E9@smtp.kernel.org \
--to=sashiko-bot@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=miquel.raynal@bootlin.com \
--cc=mwalle@kernel.org \
--cc=pratyush@kernel.org \
--cc=richard@nod.at \
--cc=robh@kernel.org \
--cc=s-k6@ti.com \
--cc=sashiko-reviews@lists.linux.dev \
--cc=takahiro.kuwano@infineon.com \
--cc=vigneshr@ti.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox