* [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-09-10 8:21 ` 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
` (8 subsequent siblings)
9 siblings, 2 replies; 22+ 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] 22+ 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
2025-09-10 8:21 ` Miquel Raynal
1 sibling, 2 replies; 22+ 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] 22+ 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; 22+ 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] 22+ 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
2025-09-10 8:07 ` Miquel Raynal
0 siblings, 2 replies; 22+ 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] 22+ 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
2025-09-10 8:07 ` Miquel Raynal
1 sibling, 0 replies; 22+ 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] 22+ 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
@ 2025-09-10 8:07 ` Miquel Raynal
1 sibling, 0 replies; 22+ messages in thread
From: Miquel Raynal @ 2025-09-10 8:07 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
On 14/08/2025 at 13:34:38 +01, Mark Brown <broonie@kernel.org> 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.
>
>> 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?).
Santhosh, do you need more inputs? Or can you send an updated version?
I am still thinking about the interface on the spi-mem/spi-nand side,
but please iterate so we can move forward.
Thanks,
Miquèl
^ permalink raw reply [flat|nested] 22+ 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; 22+ 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] 22+ 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-09-10 8:21 ` Miquel Raynal
2025-09-20 17:55 ` Santhosh Kumar K
1 sibling, 1 reply; 22+ messages in thread
From: Miquel Raynal @ 2025-09-10 8:21 UTC (permalink / raw)
To: Santhosh Kumar K
Cc: richard, vigneshr, broonie, tudor.ambarus, pratyush, mwalle,
p-mantena, linux-spi, linux-mtd, linux-kernel, a-dutta, u-kumar1,
praneeth
On 12/08/2025 at 01:02:10 +0530, Santhosh Kumar K <s-k6@ti.com> 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).
This is actually wrong. Tuning is way more generic than that :)
If someone wants to use a chip at a high frequency (50MHz in your case,
but whatever, there is a threshold above which additional care must be
taken), it must go through the calibration step. It does not matter in
which mode you are. Calibration would still be relevant in single SDR
mode.
This 50MHz bothered Mark because it is too Cadence specific. Maybe this
should be a controller parameter? If the spi-mem core (or even the spi
core, by extensino) sees that the design allows running at XMHz (due to
the SPI peripheral properties or simply the absence of any limitation),
and if the controller states that it requires an extra tuning step above
YMHz (and X > Y), then it launches the calibration.
From a core perspective, I would like the calibration hook to be as
simple as possible, because what "calibration" means is highly
controller and chip specific.
The Cadence SPI controller driver could request the pattern through
the nvmem interface or maybe we can even include it in the kernel
through some type of firmware interface (it could be stored anywhere)
and if it gets it, it writes it to the device cache. Once done, it takes
the fastest available read operation available for the chip and performs
its calibration.
The calibration hook no longer needs anything SPI driver specific. I
don't know if still requires anything chip specific though (like the
optimal read operation), but can you please try implementing that and
then we'll discuss this further.
Thanks,
Miquèl
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 01/10] spi: spi-mem: Introduce support for tuning controller
2025-09-10 8:21 ` Miquel Raynal
@ 2025-09-20 17:55 ` Santhosh Kumar K
2025-10-28 15:41 ` Miquel Raynal
0 siblings, 1 reply; 22+ messages in thread
From: Santhosh Kumar K @ 2025-09-20 17:55 UTC (permalink / raw)
To: Miquel Raynal
Cc: richard, vigneshr, broonie, tudor.ambarus, pratyush, mwalle,
p-mantena, linux-spi, linux-mtd, linux-kernel, a-dutta, u-kumar1,
praneeth, s-k6
Hello,
On 10/09/25 13:51, Miquel Raynal wrote:
> On 12/08/2025 at 01:02:10 +0530, Santhosh Kumar K <s-k6@ti.com> 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).
Apologies for the delay in response - I started prototyping new solution
based on our discussions earlier, which took some additional time.
>
> This is actually wrong. Tuning is way more generic than that :)
>
> If someone wants to use a chip at a high frequency (50MHz in your case,
> but whatever, there is a threshold above which additional care must be
> taken), it must go through the calibration step. It does not matter in
> which mode you are. Calibration would still be relevant in single SDR
> mode.
>
>
> This 50MHz bothered Mark because it is too Cadence specific. Maybe this
> should be a controller parameter? If the spi-mem core (or even the spi
> core, by extensino) sees that the design allows running at XMHz (due to
> the SPI peripheral properties or simply the absence of any limitation),
> and if the controller states that it requires an extra tuning step above
> YMHz (and X > Y), then it launches the calibration.
>
> From a core perspective, I would like the calibration hook to be as
> simple as possible, because what "calibration" means is highly
> controller and chip specific.
I understand the concern here.
Let me point out the options for launching the tuning procedure, along
with the issues in each approach.
Option 1: Launch tuning as part of spi_mem_exec_op()
- After spi_mem_access_start(), introduce a spi_mem_needs_tuning()
check (a new callback to SPI MEM controller) to check whether the
current op requires tuning
- If yes, we call spi_mem_execute_tuning()
- on success, mark tuning complete in a flag within SPI MEM
Controller private data
- on failure, we attempt a fallback by calling
spi_mem_adjust_op_freq() and drop to a lower supported frequency
Option 2: Launch tuning within spi_controller->exec_op() implementation
- Very similar to option 1, except that the spi_mem_execute_tuning()
is triggered from within the controller's exec_op() implementation
(no need for spi_mem_needs_tuning())
Drawbacks in option 1 and 2:
- Tuning requires multiple reads of a known pattern, but the flash
may not always be in a state to allow read commands
- No fallback on failures, can't make flash-specific adjustments in
case of a tuning failure
- No access to write_op() to write known pattern temporarily to an
on-die cache. Pattern needs to be always burnt into the flash
- Plus, in option 2 - we can't call spi_mem_adjust_op_freq()
While the need for tuning is dictated by Controller specific
characteristics the ops (and state of the chip) required to complete
tuning is under the control of spi-mem users (spi-nand/spi-nor).
So, it's impossible to achieve tuning without the help of spi-mem users.
So, Option 3: Launch from SPI MEM clients
(mtd/nand/spi or mtd/spi-nor, etc.,)
- Once the spi-mem chip is completely enumerated and best read and
write ops are chosen call spi_mem_needs_tuning(read_op, write_op) as
a part of .probe()
- If tuning is required, call
spi_mem_execute_tuning(read_op, write_op)
- If only read_op is provided, it implies the tuning pattern is
pre-flashed to the partition
- On tuning failure, retry by re-running spi_mem_needs_tuning() with
the second best set of ops (max throughput - 1)
With option 3, spi_mem users are limited to calling
spi_mem_needs_tuning() and spi_mem_execute_tuning(). Rest is hidden
within the controller drivers. If spi-mem users change read/write ops,
the above sequence can be re-issued.
The controller can store the read_op and write_op in case of a tuning
success and periodically re-run tuning, ensuring we always have valid
tuning parameters.
One concern with option 3 is that we may not be able to make use of
static data on certain flash as tuning patterns (like reading parameter
page or SFDP table for tuning instead of controller specific attack
patterns).
Please let me know your thoughts on which of these directions makes the
most sense.
Thanks,
Santhosh.
^ permalink raw reply [flat|nested] 22+ messages in thread* Re: [RFC PATCH 01/10] spi: spi-mem: Introduce support for tuning controller
2025-09-20 17:55 ` Santhosh Kumar K
@ 2025-10-28 15:41 ` Miquel Raynal
2025-11-05 8:55 ` Santhosh Kumar K
0 siblings, 1 reply; 22+ messages in thread
From: Miquel Raynal @ 2025-10-28 15:41 UTC (permalink / raw)
To: Santhosh Kumar K
Cc: richard, vigneshr, broonie, tudor.ambarus, pratyush, mwalle,
p-mantena, linux-spi, linux-mtd, linux-kernel, a-dutta, u-kumar1,
praneeth
Hello Santhosh,
On 20/09/2025 at 23:25:31 +0530, Santhosh Kumar K <s-k6@ti.com> wrote:
> Hello,
>
> On 10/09/25 13:51, Miquel Raynal wrote:
>> On 12/08/2025 at 01:02:10 +0530, Santhosh Kumar K <s-k6@ti.com> 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).
>
> Apologies for the delay in response - I started prototyping new solution
> based on our discussions earlier, which took some additional time.
My turn to apologize for the delay, especially since your feedback is
very complete.
>> This is actually wrong. Tuning is way more generic than that :)
>> If someone wants to use a chip at a high frequency (50MHz in your
>> case,
>> but whatever, there is a threshold above which additional care must be
>> taken), it must go through the calibration step. It does not matter in
>> which mode you are. Calibration would still be relevant in single SDR
>> mode.
>> This 50MHz bothered Mark because it is too Cadence specific. Maybe
>> this
>> should be a controller parameter? If the spi-mem core (or even the spi
>> core, by extensino) sees that the design allows running at XMHz (due to
>> the SPI peripheral properties or simply the absence of any limitation),
>> and if the controller states that it requires an extra tuning step above
>> YMHz (and X > Y), then it launches the calibration.
>> From a core perspective, I would like the calibration hook to be as
>> simple as possible, because what "calibration" means is highly
>> controller and chip specific.
>
> I understand the concern here.
>
> Let me point out the options for launching the tuning procedure, along
> with the issues in each approach.
Very good summary.
> Option 1: Launch tuning as part of spi_mem_exec_op()
> - After spi_mem_access_start(), introduce a spi_mem_needs_tuning()
> check (a new callback to SPI MEM controller) to check whether the
> current op requires tuning
> - If yes, we call spi_mem_execute_tuning()
> - on success, mark tuning complete in a flag within SPI MEM
> Controller private data
> - on failure, we attempt a fallback by calling
> spi_mem_adjust_op_freq() and drop to a lower supported frequency
>
> Option 2: Launch tuning within spi_controller->exec_op() implementation
> - Very similar to option 1, except that the spi_mem_execute_tuning()
> is triggered from within the controller's exec_op() implementation
> (no need for spi_mem_needs_tuning())
>
> Drawbacks in option 1 and 2:
> - Tuning requires multiple reads of a known pattern, but the flash
> may not always be in a state to allow read commands
> - No fallback on failures, can't make flash-specific adjustments in
> case of a tuning failure
> - No access to write_op() to write known pattern temporarily to an
> on-die cache. Pattern needs to be always burnt into the flash
>
> - Plus, in option 2 - we can't call spi_mem_adjust_op_freq()
Two more significant drawbacks:
- it adds an extra step in the "fast path" -maybe negligible?-
- spi_mem_exec_op()/->exec_op() are called way before being ready for
calibration.
> While the need for tuning is dictated by Controller specific
> characteristics the ops (and state of the chip) required to complete
> tuning is under the control of spi-mem users (spi-nand/spi-nor).
> So, it's impossible to achieve tuning without the help of spi-mem users.
Sounds like a constraint we can afford indeed, especially since the ops
that can be optimized, are flash specific (relatively few content to
share between spi nor and spi nand).
> So, Option 3: Launch from SPI MEM clients
> (mtd/nand/spi or mtd/spi-nor, etc.,)
> - Once the spi-mem chip is completely enumerated and best read and
> write ops are chosen call spi_mem_needs_tuning(read_op, write_op) as
> a part of .probe()
This looks like a decent place, but there is one limitation to
workaround: picking best read and write ops require knowing what the
controller is capable of in terms of frequency, which means we must in
advance expect to set up calibration or not. I don't think it's a
problem, this is something we know in advance thanks to
eg. spi-max-frequency in the DT, but I still think a controller specific
"maximum frequency without calibration" capability must be carried for
the controller to decide whether this step is needed or not when asked
by the spi mem client.
> - If tuning is required, call
I guess "tuning being required" is a controller choice, based on the
target frequency for both read/write ops and the controller capability
to achieve this.
> spi_mem_execute_tuning(read_op, write_op)
> - If only read_op is provided, it implies the tuning pattern is
> pre-flashed to the partition
Interesting. I guess that lives some room for tuning PHYs during writes as
well without more core modifications later, isn't it?
> - On tuning failure, retry by re-running spi_mem_needs_tuning() with
> the second best set of ops (max throughput - 1)
I would like to challenge this need. Can the same calibration fail if
attempted multiple times (eg. because of the heat?) If yes, then we need
a fallback indeed. Otherwise, I'd be in favor of just failing the
probe. Calibration is an opt-in -> users must allow a higher frequency
than they use to in order to enable the feature?
> With option 3, spi_mem users are limited to calling
> spi_mem_needs_tuning() and spi_mem_execute_tuning().
I would even go for a single spi_mem_tune_phy()? Or is there a point in
having two helpers?
> Rest is hidden
> within the controller drivers. If spi-mem users change read/write ops,
> the above sequence can be re-issued.
I don't have use cases for that in mind, but why not.
> The controller can store the read_op and write_op in case of a tuning
> success and periodically re-run tuning, ensuring we always have valid
> tuning parameters.
You'll have to make sure you only use PHY calibration for the ops that
have been used for the tuning though, because for example as I am
working on octal DDR support: during S2RAM there may be the need for
returning to SDR mode, which in turns will have to work without the
tuning (tuning parameters will be incorrect for this mode for the time
we run slowly). So either the controller knows which operation should
enable PHY optimizations, or we must perform the whole calibration again
every time we suspend (meh).
> One concern with option 3 is that we may not be able to make use of
> static data on certain flash as tuning patterns (like reading parameter
> page or SFDP table for tuning instead of controller specific attack
> patterns).
This is true, I know some devices can send patterns during dummy cycles
by I have no idea how powerful that is, nor if it can actually be used
in Linux. One need a controller that is aware of these bits and can
itself adjust/fine tune its own configuration. For now, I propose to let
this aside until we get real hardware that can be tested.
> Please let me know your thoughts on which of these directions makes the
> most sense.
Let's got for option 3. I'm eager to see this moving forward!
Thanks, Miquèl
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 01/10] spi: spi-mem: Introduce support for tuning controller
2025-10-28 15:41 ` Miquel Raynal
@ 2025-11-05 8:55 ` Santhosh Kumar K
2025-11-05 9:35 ` Miquel Raynal
0 siblings, 1 reply; 22+ messages in thread
From: Santhosh Kumar K @ 2025-11-05 8:55 UTC (permalink / raw)
To: Miquel Raynal
Cc: richard, vigneshr, broonie, tudor.ambarus, pratyush, mwalle,
p-mantena, linux-spi, linux-mtd, linux-kernel, a-dutta, u-kumar1,
praneeth, s-k6
Hello Miquel,
On 28/10/25 21:11, Miquel Raynal wrote:
> Hello Santhosh,
>
> On 20/09/2025 at 23:25:31 +0530, Santhosh Kumar K <s-k6@ti.com> wrote:
>
>> Hello,
>>
>> On 10/09/25 13:51, Miquel Raynal wrote:
>>> On 12/08/2025 at 01:02:10 +0530, Santhosh Kumar K <s-k6@ti.com> 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).
>>
>> Apologies for the delay in response - I started prototyping new solution
>> based on our discussions earlier, which took some additional time.
>
> My turn to apologize for the delay, especially since your feedback is
> very complete.
>
>>> This is actually wrong. Tuning is way more generic than that :)
>>> If someone wants to use a chip at a high frequency (50MHz in your
>>> case,
>>> but whatever, there is a threshold above which additional care must be
>>> taken), it must go through the calibration step. It does not matter in
>>> which mode you are. Calibration would still be relevant in single SDR
>>> mode.
>>> This 50MHz bothered Mark because it is too Cadence specific. Maybe
>>> this
>>> should be a controller parameter? If the spi-mem core (or even the spi
>>> core, by extensino) sees that the design allows running at XMHz (due to
>>> the SPI peripheral properties or simply the absence of any limitation),
>>> and if the controller states that it requires an extra tuning step above
>>> YMHz (and X > Y), then it launches the calibration.
>>> From a core perspective, I would like the calibration hook to be as
>>> simple as possible, because what "calibration" means is highly
>>> controller and chip specific.
>>
>> I understand the concern here.
>>
>> Let me point out the options for launching the tuning procedure, along
>> with the issues in each approach.
>
> Very good summary.
Thanks you for the review!
>
>> Option 1: Launch tuning as part of spi_mem_exec_op()
>> - After spi_mem_access_start(), introduce a spi_mem_needs_tuning()
>> check (a new callback to SPI MEM controller) to check whether the
>> current op requires tuning
>> - If yes, we call spi_mem_execute_tuning()
>> - on success, mark tuning complete in a flag within SPI MEM
>> Controller private data
>> - on failure, we attempt a fallback by calling
>> spi_mem_adjust_op_freq() and drop to a lower supported frequency
>>
>> Option 2: Launch tuning within spi_controller->exec_op() implementation
>> - Very similar to option 1, except that the spi_mem_execute_tuning()
>> is triggered from within the controller's exec_op() implementation
>> (no need for spi_mem_needs_tuning())
>>
>> Drawbacks in option 1 and 2:
>> - Tuning requires multiple reads of a known pattern, but the flash
>> may not always be in a state to allow read commands
>> - No fallback on failures, can't make flash-specific adjustments in
>> case of a tuning failure
>> - No access to write_op() to write known pattern temporarily to an
>> on-die cache. Pattern needs to be always burnt into the flash
>>
>> - Plus, in option 2 - we can't call spi_mem_adjust_op_freq()
>
> Two more significant drawbacks:
> - it adds an extra step in the "fast path" -maybe negligible?-
> - spi_mem_exec_op()/->exec_op() are called way before being ready for
> calibration.
Yeah, it's negligible - the tuning runs only the first time that op is
called, and we just store and reuse the parameters after that.
>
>> While the need for tuning is dictated by Controller specific
>> characteristics the ops (and state of the chip) required to complete
>> tuning is under the control of spi-mem users (spi-nand/spi-nor).
>> So, it's impossible to achieve tuning without the help of spi-mem users.
>
> Sounds like a constraint we can afford indeed, especially since the ops
> that can be optimized, are flash specific (relatively few content to
> share between spi nor and spi nand).
True!
>
>> So, Option 3: Launch from SPI MEM clients
>> (mtd/nand/spi or mtd/spi-nor, etc.,)
>> - Once the spi-mem chip is completely enumerated and best read and
>> write ops are chosen call spi_mem_needs_tuning(read_op, write_op) as
>> a part of .probe()
>
> This looks like a decent place, but there is one limitation to
> workaround: picking best read and write ops require knowing what the
> controller is capable of in terms of frequency, which means we must in
> advance expect to set up calibration or not. I don't think it's a
> problem, this is something we know in advance thanks to
> eg. spi-max-frequency in the DT, but I still think a controller specific
> "maximum frequency without calibration" capability must be carried for
> the controller to decide whether this step is needed or not when asked
> by the spi mem client.
Makes sense! We can maintain such a property to directly check whether
tuning is needed or not. I'll add that in.
>
>> - If tuning is required, call
>
> I guess "tuning being required" is a controller choice, based on the
> target frequency for both read/write ops and the controller capability
> to achieve this.
>
>> spi_mem_execute_tuning(read_op, write_op)
>> - If only read_op is provided, it implies the tuning pattern is
>> pre-flashed to the partition
>
> Interesting. I guess that lives some room for tuning PHYs during writes as
> well without more core modifications later, isn't it?
Yeah, you're right. See Patch 08/10 - we enable PHY for data writes.
>
>> - On tuning failure, retry by re-running spi_mem_needs_tuning() with
>> the second best set of ops (max throughput - 1)
>
> I would like to challenge this need. Can the same calibration fail if
> attempted multiple times (eg. because of the heat?) If yes, then we need
> a fallback indeed. Otherwise, I'd be in favor of just failing the
> probe. Calibration is an opt-in -> users must allow a higher frequency
> than they use to in order to enable the feature?
It's possible the same calibration will fail intermittently for
different reasons (temperature changes, as you mentioned). If tuning
fails, the driver should fallback to the non-PHY frequency so the flash
continues operating with slower reads/writes rather than failing the
probe (availability should be prioritized, right?).
>
>> With option 3, spi_mem users are limited to calling
>> spi_mem_needs_tuning() and spi_mem_execute_tuning().
>
> I would even go for a single spi_mem_tune_phy()? Or is there a point in
> having two helpers?
May be not needed, we can do the check and tuning within a single
function.
>
>> Rest is hidden
>> within the controller drivers. If spi-mem users change read/write ops,
>> the above sequence can be re-issued.
>
> I don't have use cases for that in mind, but why not.
>
>> The controller can store the read_op and write_op in case of a tuning
>> success and periodically re-run tuning, ensuring we always have valid
>> tuning parameters.
>
> You'll have to make sure you only use PHY calibration for the ops that
> have been used for the tuning though, because for example as I am
> working on octal DDR support: during S2RAM there may be the need for
> returning to SDR mode, which in turns will have to work without the
> tuning (tuning parameters will be incorrect for this mode for the time
> we run slowly). So either the controller knows which operation should
> enable PHY optimizations, or we must perform the whole calibration again
> every time we suspend (meh).
The controller's calibration logic is straightforward - tuning is
required for higher frequencies and not for lower ones. So we need to
perform calibration each time we switch modes.
In the future, we could enhance this by storing the operations that
require tuning along with their respective calibrated parameters within
the controller.
>
>> One concern with option 3 is that we may not be able to make use of
>> static data on certain flash as tuning patterns (like reading parameter
>> page or SFDP table for tuning instead of controller specific attack
>> patterns).
>
> This is true, I know some devices can send patterns during dummy cycles
> by I have no idea how powerful that is, nor if it can actually be used
> in Linux. One need a controller that is aware of these bits and can
> itself adjust/fine tune its own configuration. For now, I propose to let
> this aside until we get real hardware that can be tested.
>
>> Please let me know your thoughts on which of these directions makes the
>> most sense.
>
> Let's got for option 3. I'm eager to see this moving forward!
Yeah, I'm eager too! I'll send out v2 asap.
Thanks,
Santhosh.
>
> Thanks, Miquèl
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH 01/10] spi: spi-mem: Introduce support for tuning controller
2025-11-05 8:55 ` Santhosh Kumar K
@ 2025-11-05 9:35 ` Miquel Raynal
0 siblings, 0 replies; 22+ messages in thread
From: Miquel Raynal @ 2025-11-05 9:35 UTC (permalink / raw)
To: Santhosh Kumar K
Cc: richard, vigneshr, broonie, tudor.ambarus, pratyush, mwalle,
p-mantena, linux-spi, linux-mtd, linux-kernel, a-dutta, u-kumar1,
praneeth
Hello Santhosh,
>>> - On tuning failure, retry by re-running spi_mem_needs_tuning() with
>>> the second best set of ops (max throughput - 1)
>> I would like to challenge this need. Can the same calibration fail if
>> attempted multiple times (eg. because of the heat?) If yes, then we need
>> a fallback indeed. Otherwise, I'd be in favor of just failing the
>> probe. Calibration is an opt-in -> users must allow a higher frequency
>> than they use to in order to enable the feature?
>
> It's possible the same calibration will fail intermittently for
> different reasons (temperature changes, as you mentioned). If tuning
> fails, the driver should fallback to the non-PHY frequency so the flash
> continues operating with slower reads/writes rather than failing the
> probe (availability should be prioritized, right?).
Agreed, if the tuning may fail we must fallback in this case. However
there is another situation that must be handled in this case: once
tuning is done and we want to use PHY-optimized paths, we must fallback
to more basic/slower reads if for some external reason, they start
failing, right?
The obvious choice in this case would be to let this error handling to
the controller driver. Re-using the same operation at a lower speed
would be suboptimal, because the fastest operation at a high speed might
not be the most efficient at slower speeds due to the number of dummy
cycles needed,. But I believe this is negligible based on the fact that
we already are in degraded mode at that stage.
However, this may conflict with:
- read retries
- continuous reads (?)
So in practice the fallback might be needed on the SPI NAND/NOR side
(this can be further discussed).
But once we solve this, comes a similar problem on the write side. How
do we know if a write will or did fail because of a temperature change?
What may be the heuristics to fallback in this case?
Thanks,
Miquèl
^ permalink raw reply [flat|nested] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ 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; 22+ 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] 22+ messages in thread