Starting with the AST2600 platform, most platfom manufacturers have
adopted more complex board designs and signal routing, making SPI
timing calibration increasingly sensitive and critical. Previously,
the driver selected the first "PASS" timing point during calibration,
which may not yield the most stable result.
This patch introduces a more robust calibration method:
- It evaluates all combinations of HCLK sample point delay and DI input
delay. The results are stored in a 2D buffer for further comparison.
- Because the timing delay behavior is non-linear across HCLK sample
points, the optimal timing point is selected as the center of the
longest consecutive "PASS" interval within a single HCLK sample
point row.
This approach ensures better stability and precision in SPI read timing,
especially under complex signal integrity conditions.
Signed-off-by: Chin-Ting Kuo <chin-ting_kuo@aspeedtech.com>
---
drivers/spi/spi-aspeed-smc.c | 70 +++++++++++++++++++++++++++++-------
1 file changed, 58 insertions(+), 12 deletions(-)
diff --git a/drivers/spi/spi-aspeed-smc.c b/drivers/spi/spi-aspeed-smc.c
index 9c54c05da3cc..d2d9e13e9bda 100644
--- a/drivers/spi/spi-aspeed-smc.c
+++ b/drivers/spi/spi-aspeed-smc.c
@@ -1162,21 +1162,57 @@ static int aspeed_spi_do_calibration(struct aspeed_spi_chip *chip)
#define TIMING_DELAY_DI BIT(3)
#define TIMING_DELAY_HCYCLE_MAX 5
+#define TIMING_DELAY_INPUT_MAX 16
#define TIMING_REG_AST2600(chip) \
((chip)->aspi->regs + (chip)->aspi->data->timing + \
(chip)->cs * 4)
+/*
+ * This function returns the center point of the longest
+ * continuous "pass" interval within the buffer. The interval
+ * must contains the highest number of consecutive "pass"
+ * results and not span across multiple rows.
+ */
+static u32 aspeed_spi_ast2600_optimized_timing(u32 rows, u32 cols,
+ u8 buf[rows][cols])
+{
+ int r = 0, c = 0;
+ int max = 0;
+ int i, j;
+
+ for (i = 0; i < rows; i++) {
+ for (j = 0; j < cols;) {
+ int k = j;
+
+ while (k < cols && buf[i][k])
+ k++;
+
+ if (k - j > max) {
+ max = k - j;
+ r = i;
+ c = j + (k - j) / 2;
+ }
+
+ j = k + 1;
+ }
+ }
+
+ return max > 4 ? r * cols + c : 0;
+}
+
static int aspeed_spi_ast2600_calibrate(struct aspeed_spi_chip *chip, u32 hdiv,
const u8 *golden_buf, u8 *test_buf)
{
struct aspeed_spi *aspi = chip->aspi;
int hcycle;
+ int delay_ns;
u32 shift = (hdiv - 2) << 3;
- u32 mask = ~(0xfu << shift);
+ u32 mask = ~(0xffu << shift);
u32 fread_timing_val = 0;
+ u8 calib_res[6][17] = {0};
+ u32 calib_point;
for (hcycle = 0; hcycle <= TIMING_DELAY_HCYCLE_MAX; hcycle++) {
- int delay_ns;
bool pass = false;
fread_timing_val &= mask;
@@ -1189,14 +1225,14 @@ static int aspeed_spi_ast2600_calibrate(struct aspeed_spi_chip *chip, u32 hdiv,
" * [%08x] %d HCLK delay, DI delay none : %s",
fread_timing_val, hcycle, pass ? "PASS" : "FAIL");
if (pass)
- return 0;
+ calib_res[hcycle][0] = 1;
/* Add DI input delays */
fread_timing_val &= mask;
fread_timing_val |= (TIMING_DELAY_DI | hcycle) << shift;
- for (delay_ns = 0; delay_ns < 0x10; delay_ns++) {
- fread_timing_val &= ~(0xf << (4 + shift));
+ for (delay_ns = 0; delay_ns < TIMING_DELAY_INPUT_MAX; delay_ns++) {
+ fread_timing_val &= ~(0xfu << (4 + shift));
fread_timing_val |= delay_ns << (4 + shift);
writel(fread_timing_val, TIMING_REG_AST2600(chip));
@@ -1205,18 +1241,28 @@ static int aspeed_spi_ast2600_calibrate(struct aspeed_spi_chip *chip, u32 hdiv,
" * [%08x] %d HCLK delay, DI delay %d.%dns : %s",
fread_timing_val, hcycle, (delay_ns + 1) / 2,
(delay_ns + 1) & 1 ? 5 : 5, pass ? "PASS" : "FAIL");
- /*
- * TODO: This is optimistic. We should look
- * for a working interval and save the middle
- * value in the read timing register.
- */
+
if (pass)
- return 0;
+ calib_res[hcycle][delay_ns + 1] = 1;
}
}
+ calib_point = aspeed_spi_ast2600_optimized_timing(6, 17, calib_res);
/* No good setting for this frequency */
- return -1;
+ if (calib_point == 0)
+ return -1;
+
+ hcycle = calib_point / 17;
+ delay_ns = calib_point % 17;
+
+ fread_timing_val = (TIMING_DELAY_DI | hcycle | (delay_ns << 4)) << shift;
+
+ dev_dbg(aspi->dev, "timing val: %08x, final hcycle: %d, delay_ns: %d\n",
+ fread_timing_val, hcycle, delay_ns);
+
+ writel(fread_timing_val, TIMING_REG_AST2600(chip));
+
+ return 0;
}
/*
--
2.34.1