[PATCH 2/8] spi: atcspi200: Improve clock configuration and divider logic

Leo Yu-Chi Liang ycliang at andestech.com
Fri Apr 17 04:20:58 CEST 2026


- Add default clock source (100 MHz) and max frequency (40 MHz)
  fallbacks when DT properties are missing
- Read spi-max-frequency from DT to cap the operating frequency
- Use improved round-up divider formula that selects the closest
  available frequency not exceeding the target
- Add SCLK_DIV_BYPASS (0xFF) and SCLK_DIV_MAX (0xFE) constants
- Remove mtiming field and timing restore in stop(), which was
  incorrectly resetting clock settings after every transfer
- Change clock fields from ulong to unsigned int

Signed-off-by: CL Chin-Long Wang <cl634 at andestech.com>
Signed-off-by: Leo Yu-Chi Liang <ycliang at andestech.com>
---
 drivers/spi/atcspi200_spi.c | 57 +++++++++++++++++++++++--------------
 1 file changed, 36 insertions(+), 21 deletions(-)

diff --git a/drivers/spi/atcspi200_spi.c b/drivers/spi/atcspi200_spi.c
index 8cae96ee23c..33cf7583581 100644
--- a/drivers/spi/atcspi200_spi.c
+++ b/drivers/spi/atcspi200_spi.c
@@ -18,6 +18,8 @@
 #define MAX_TRANSFER_LEN	512
 #define CHUNK_SIZE		1
 #define SPI_TIMEOUT		0x100000
+#define SPI_DEF_SRC_CLK		100000000
+#define SPI_DEF_MAX_CLK		40000000
 
 /* Register offsets */
 #define ATCSPI200_REG_FORMAT	0x10
@@ -61,15 +63,17 @@
 
 /* TIMING register fields */
 #define SCLK_DIV_MASK		GENMASK(7, 0)
+#define SCLK_DIV_BYPASS		0xFF
+#define SCLK_DIV_MAX		0xFE
 
 struct atcspi200_priv {
 	void		*regs;
 	int		to;
 	unsigned int	freq;
-	ulong		clock;
+	unsigned int	src_clk;
+	unsigned int	max_clk;
 	unsigned int	mode;
 	u8		num_cs;
-	unsigned int	mtiming;
 	size_t		cmd_len;
 	u8		cmd_buf[16];
 	size_t		data_len;
@@ -93,22 +97,28 @@ static inline void atcspi200_write(struct atcspi200_priv *priv, u32 offset,
 static int atcspi200_hw_set_speed(struct atcspi200_priv *priv)
 {
 	u32 tm;
-	u8 div;
+	u32 div;
+	u32 spi_sclk;
 
-	tm = atcspi200_read(priv, ATCSPI200_REG_TIMING);
-	tm &= ~SCLK_DIV_MASK;
+	tm = atcspi200_read(priv, ATCSPI200_REG_TIMING) & ~SCLK_DIV_MASK;
 
-	if (priv->freq >= priv->clock) {
-		div = 0xff;
+	spi_sclk = min(priv->freq, priv->max_clk);
+
+	/*
+	 * SCLK_DIV = 0xFF bypasses the divider, giving SCLK = src_clk.
+	 * Otherwise SCLK = src_clk / (2 * (SCLK_DIV + 1)).
+	 */
+	if (spi_sclk >= priv->src_clk) {
+		div = SCLK_DIV_BYPASS;
 	} else {
-		for (div = 0; div < 0xff; div++) {
-			if (priv->freq >= priv->clock / (2 * (div + 1)))
-				break;
+		div = DIV_ROUND_UP(priv->src_clk, spi_sclk * 2) - 1;
+		if (div > SCLK_DIV_MAX) {
+			pr_err("Unsupported SPI clock %u\n", spi_sclk);
+			return -EINVAL;
 		}
 	}
 
-	tm |= div;
-	atcspi200_write(priv, ATCSPI200_REG_TIMING, tm);
+	atcspi200_write(priv, ATCSPI200_REG_TIMING, tm | div);
 
 	return 0;
 }
@@ -166,7 +176,6 @@ static int atcspi200_hw_start(struct atcspi200_priv *priv)
 
 static int atcspi200_hw_stop(struct atcspi200_priv *priv)
 {
-	atcspi200_write(priv, ATCSPI200_REG_TIMING, priv->mtiming);
 	while ((atcspi200_read(priv, ATCSPI200_REG_STATUS) & SPIBSY) &&
 	       priv->to--)
 		if (!priv->to)
@@ -353,14 +362,21 @@ static int atcspi200_spi_get_clk(struct udevice *bus)
 	int ret;
 
 	ret = clk_get_by_index(bus, 0, &clk);
-	if (ret)
-		return -EINVAL;
-
-	clk_rate = clk_get_rate(&clk);
-	if (!clk_rate)
-		return -EINVAL;
+	if (ret) {
+		dev_warn(bus, "no clock node, using default %d Hz\n",
+			 SPI_DEF_SRC_CLK);
+		priv->src_clk = SPI_DEF_SRC_CLK;
+	} else {
+		clk_rate = clk_get_rate(&clk);
+		if (!clk_rate) {
+			dev_err(bus, "invalid clock rate\n");
+			return -EINVAL;
+		}
+		priv->src_clk = clk_rate;
+	}
 
-	priv->clock = clk_rate;
+	priv->max_clk = dev_read_u32_default(bus, "spi-max-frequency",
+					      SPI_DEF_MAX_CLK);
 
 	return 0;
 }
@@ -371,7 +387,6 @@ static int atcspi200_spi_probe(struct udevice *bus)
 
 	priv->to = SPI_TIMEOUT;
 	priv->max_transfer_length = MAX_TRANSFER_LEN;
-	priv->mtiming = atcspi200_read(priv, ATCSPI200_REG_TIMING);
 	atcspi200_spi_get_clk(bus);
 
 	return 0;
-- 
2.34.1



More information about the U-Boot mailing list