From 62df72a0ab22b3377aeba3b2159ab35274a4106f Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 18 Jun 2025 14:14:18 +0200 Subject: spi: spi-mem: Use picoseconds for calculating the op durations spi_mem_calc_op_duration() is deriving the duration of a specific op, by multiplying the number of cycles with the time a cycle will last. This time was measured in nanoseconds, which means at high frequencies the delta between two frequencies might not be properly catch due to roundings. For instance, the Winbond driver has a changing number of dummy cycles depending on the speed, adding +8 dummy cycles when running at 166MHz compared to 162MHz. Both frequencies would lead to using a 6ns delay per cycle for the op duration computation, whereas in practice there is a small difference which actually offsets the number of extra dummy cycles on a normal page read. Augmenting the precision of the calculation by using picoseconds prevents selecting a lower frequency if we can do slightly better with another frequency involving more cycles. As a result, the above situation leads to comparing cycles of 6024 and 6172 picoseconds which leads to picking the most efficient variant. Reviewed-by: Mark Brown Signed-off-by: Miquel Raynal --- drivers/spi/spi-mem.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index 5db0639d3b01..c42c227eb2a2 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -591,9 +591,11 @@ EXPORT_SYMBOL_GPL(spi_mem_adjust_op_freq); u64 spi_mem_calc_op_duration(struct spi_mem_op *op) { u64 ncycles = 0; - u32 ns_per_cycles; + u64 ps_per_cycles, duration; + + ps_per_cycles = 1000000000000ULL; + do_div(ps_per_cycles, op->max_freq); - ns_per_cycles = 1000000000 / op->max_freq; ncycles += ((op->cmd.nbytes * 8) / op->cmd.buswidth) / (op->cmd.dtr ? 2 : 1); ncycles += ((op->addr.nbytes * 8) / op->addr.buswidth) / (op->addr.dtr ? 2 : 1); @@ -603,7 +605,12 @@ u64 spi_mem_calc_op_duration(struct spi_mem_op *op) ncycles += ((op->data.nbytes * 8) / op->data.buswidth) / (op->data.dtr ? 2 : 1); - return ncycles * ns_per_cycles; + /* Derive the duration in ps */ + duration = ncycles * ps_per_cycles; + /* Convert into ns */ + do_div(duration, 1000); + + return duration; } EXPORT_SYMBOL_GPL(spi_mem_calc_op_duration); -- cgit v1.2.3 From a11a51896572273d04a9f6011ad22738c52ba554 Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Wed, 4 Jun 2025 15:52:19 +0200 Subject: spi: spi-mem: Take into account the actual maximum frequency In order to pick the best variant, the duration of each typical operation is derived and then compared. These durations are based on the maximum capabilities of the chips, which are commonly the limiting factors. However there are other possible limiting pieces, such as the hardware layout, EMC considerations and in some cases, the SPI controller itself. We need to take this into account to further refine our variant choice, so let's use the actual frequency that will be used for the operation instead of the theoretical maximum. Signed-off-by: Miquel Raynal Reviewed-by: Mark Brown --- drivers/spi/spi-mem.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index c42c227eb2a2..d3b7e857b377 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -586,15 +586,25 @@ EXPORT_SYMBOL_GPL(spi_mem_adjust_op_freq); * accurate, all these combinations should be rated (eg. with a time estimate) * and the best pick should be taken based on these calculations. * - * Returns a ns estimate for the time this op would take. + * Returns a ns estimate for the time this op would take, except if no + * frequency limit has been set, in this case we return the number of + * cycles nevertheless to allow callers to distinguish which operation + * would be the fastest at iso-frequency. */ -u64 spi_mem_calc_op_duration(struct spi_mem_op *op) +u64 spi_mem_calc_op_duration(struct spi_mem *mem, struct spi_mem_op *op) { u64 ncycles = 0; u64 ps_per_cycles, duration; - ps_per_cycles = 1000000000000ULL; - do_div(ps_per_cycles, op->max_freq); + spi_mem_adjust_op_freq(mem, op); + + if (op->max_freq) { + ps_per_cycles = 1000000000000ULL; + do_div(ps_per_cycles, op->max_freq); + } else { + /* In this case, the unit is no longer a time unit */ + ps_per_cycles = 1; + } ncycles += ((op->cmd.nbytes * 8) / op->cmd.buswidth) / (op->cmd.dtr ? 2 : 1); ncycles += ((op->addr.nbytes * 8) / op->addr.buswidth) / (op->addr.dtr ? 2 : 1); -- cgit v1.2.3