mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Changhuang Liang <changhuang.liang@starfivetech.com>
To: Pratyush Yadav <pratyush@kernel.org>,
	Michael Walle <mwalle@kernel.org>,
	Takahiro Kuwano <takahiro.kuwano@infineon.com>,
	Mark Brown <broonie@kernel.org>,
	Miquel Raynal <miquel.raynal@bootlin.com>,
	Richard Weinberger <richard@nod.at>,
	Vignesh Raghavendra <vigneshr@ti.com>
Cc: linux-spi@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-mtd@lists.infradead.org,
	Changhuang Liang <changhuang.liang@starfivetech.com>
Subject: [PATCH v1 1/4] spi: dw: Add DDR (Dual Data-Rate) support for enhanced SPI mode
Date: Thu, 22 Oct 2026 03:26:11 -0700	[thread overview]
Message-ID: <20261022102614.75696-2-changhuang.liang@starfivetech.com> (raw)
In-Reply-To: <20261022102614.75696-1-changhuang.liang@starfivetech.com>

Add support for DDR (Dual Data-Rate) transfers in the enhanced SPI
mode of the DW SPI controller.

The enhanced SPI control register (SPI_CTRLR0) has dedicated fields to
enable DDR mode for the instruction phase (INST_DDR_EN) and for the
address/dummy/data phases (SPI_DDR_EN). Populate these from the
corresponding dtr flags of the spi_mem_op.

For DDR transfers, the dummy phase consumes two bits per clock per
line, so the computed wait cycles must be halved. Factor the wait cycle
calculation into a helper, dw_spi_enh_wait_cycles(), so that both the
supports check and the exec path use the same value.

When SPI DDR is enabled, program the DDR_DRIVE_EDGE register. The
recommended TDE value is clk_div / 4, clamped to clk_div / 2 - 1 and to
the field width.

Finally, advertise DTR support to the SPI memory layer through a
dedicated dw_spi_enh_mem_caps, selected when the controller has
DW_SPI_CAP_EMODE, so that spi-mem may issue DDR operations.

Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com>
---
 drivers/spi/spi-dw-core.c | 52 +++++++++++++++++++++++++++++++++------
 drivers/spi/spi-dw.h      |  6 +++++
 2 files changed, 51 insertions(+), 7 deletions(-)

diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c
index 47434fa78e4b..6ff142c8a6bd 100644
--- a/drivers/spi/spi-dw-core.c
+++ b/drivers/spi/spi-dw-core.c
@@ -46,6 +46,8 @@ struct dw_spi_enh_cfg {
 	u8 inst_l;
 	u8 addr_l;
 	u8 trans_t;
+	bool inst_ddr;
+	bool spi_ddr;
 };

 #ifdef CONFIG_DEBUG_FS
@@ -418,6 +420,7 @@ static void dw_spi_update_config(struct dw_spi *dws, struct spi_device *spi,
 	u32 cr0 = chip->cr0;
 	u32 speed_hz;
 	u16 clk_div;
+	u32 tde = 0;

 	/* CTRLR0[ 4/3: 0] or CTRLR0[ 20: 16] Data Frame Size */
 	cr0 |= (cfg->dfs - 1) << dws->dfs_offset;
@@ -468,11 +471,26 @@ static void dw_spi_update_config(struct dw_spi *dws, struct spi_device *spi,

 	if (enh_cfg) {
 		cr0 = DW_SPI_ENH_CTRLR0_CLK_STRETCH_EN;
+		if (enh_cfg->inst_ddr)
+			cr0 |= DW_SPI_ENH_CTRLR0_INST_DDR_EN;
+		if (enh_cfg->spi_ddr)
+			cr0 |= DW_SPI_ENH_CTRLR0_SPI_DDR_EN;
 		cr0 |= FIELD_PREP(DW_SPI_ENH_CTRLR0_WAIT_CYCLE_MASK, enh_cfg->wait_c);
 		cr0 |= FIELD_PREP(DW_SPI_ENH_CTRLR0_INST_L_MASK, enh_cfg->inst_l);
 		cr0 |= FIELD_PREP(DW_SPI_ENH_CTRLR0_ADDR_L_MASK, enh_cfg->addr_l);
 		cr0 |= FIELD_PREP(DW_SPI_ENH_CTRLR0_TRANS_TYPE_MASK, enh_cfg->trans_t);
 		dw_writel(dws, DW_SPI_SPI_CTRLR0, cr0);
+
+		/*
+		 * The recommended value of TDE is clk_div / 4, and it must not exceed
+		 * SCKDV / 2 - 1
+		 */
+		if (enh_cfg->spi_ddr)
+			tde = min3(clk_div / 4U, clk_div / 2U - 1,
+				   FIELD_MAX(DW_SPI_DDR_DRIVE_EDGE_TDE_MASK));
+
+		dw_writel(dws, DW_SPI_DDR_DRIVE_EDGE,
+			  FIELD_PREP(DW_SPI_DDR_DRIVE_EDGE_TDE_MASK, tde));
 	}
 }

@@ -656,6 +674,21 @@ static int dw_spi_adjust_mem_op_size(struct spi_mem *mem, struct spi_mem_op *op)
 	return 0;
 }

+static u32 dw_spi_enh_wait_cycles(const struct spi_mem_op *op)
+{
+	u32 wait_c;
+
+	if (!op->dummy.nbytes || !op->dummy.buswidth)
+		return 0;
+
+	wait_c = op->dummy.nbytes * BITS_PER_BYTE / op->dummy.buswidth;
+	/* DTR transfers two bits per clock on each line. */
+	if (op->dummy.dtr)
+		wait_c >>= 1;
+
+	return wait_c;
+}
+
 static bool dw_spi_supports_enh_mem_op(struct spi_mem *mem,
 				       const struct spi_mem_op *op)
 {
@@ -678,10 +711,7 @@ static bool dw_spi_supports_enh_mem_op(struct spi_mem *mem,
 	if (op->dummy.nbytes != 0 && op->data.dir == SPI_MEM_DATA_OUT)
 		return false;

-	/* WAIT_CYCLES is a 5-bit field in SPI_CTRLR0 */
-	if (op->dummy.nbytes != 0 &&
-	    op->dummy.nbytes * BITS_PER_BYTE / op->dummy.buswidth >
-	    FIELD_MAX(DW_SPI_ENH_CTRLR0_WAIT_CYCLE_MASK))
+	if (dw_spi_enh_wait_cycles(op) > FIELD_MAX(DW_SPI_ENH_CTRLR0_WAIT_CYCLE_MASK))
 		return false;

 	if ((dws->quirk_flags & DW_SPI_QUIRK_JHB100) && op->addr.nbytes &&
@@ -1074,8 +1104,10 @@ static int dw_spi_exec_enh_mem_op(struct spi_mem *mem, const struct spi_mem_op *
 	else
 		enh_cfg.inst_l = DW_SPI_ENH_CTRLR0_INST_L_INST_L0;

-	if (op->dummy.buswidth)
-		enh_cfg.wait_c = op->dummy.nbytes * BITS_PER_BYTE / op->dummy.buswidth;
+	enh_cfg.wait_c = dw_spi_enh_wait_cycles(op);
+
+	enh_cfg.inst_ddr = op->cmd.dtr;
+	enh_cfg.spi_ddr = op->addr.dtr || op->dummy.dtr || op->data.dtr;

 	dw_spi_enable_chip(dws, 0);

@@ -1383,6 +1415,11 @@ static const struct spi_controller_mem_caps dw_spi_mem_caps = {
 	.per_op_freq = true,
 };

+static const struct spi_controller_mem_caps dw_spi_enh_mem_caps = {
+	.per_op_freq = true,
+	.dtr = true,
+};
+
 int dw_spi_add_controller(struct device *dev, struct dw_spi *dws)
 {
 	struct spi_controller *ctlr;
@@ -1439,7 +1476,8 @@ int dw_spi_add_controller(struct device *dev, struct dw_spi *dws)
 			ctlr->set_cs = dw_spi_set_cs;
 		if (dws->mem_ops.exec_op) {
 			ctlr->mem_ops = &dws->mem_ops;
-			ctlr->mem_caps = &dw_spi_mem_caps;
+			ctlr->mem_caps = (dws->caps & DW_SPI_CAP_EMODE) ?
+					 &dw_spi_enh_mem_caps : &dw_spi_mem_caps;
 		}
 		ctlr->max_speed_hz = dws->max_freq;
 		ctlr->flags = SPI_CONTROLLER_GPIO_SS;
diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h
index c16225c5c887..67b4e0098ac3 100644
--- a/drivers/spi/spi-dw.h
+++ b/drivers/spi/spi-dw.h
@@ -66,6 +66,7 @@
 #define DW_SPI_RX_SAMPLE_DLY		0xf0
 #define DW_SPI_SPI_CTRLR0		0xf4
 #define DW_SPI_CS_OVERRIDE		0xf4
+#define DW_SPI_DDR_DRIVE_EDGE		0xf8

 /* Register offsets (StarFive JHB100 DWC SSI IP-cores) */
 #define DW_SPI_JHB100_INST		0x1000
@@ -139,6 +140,8 @@

 /* Bit fields in SPI_CTRLR0 */
 #define DW_SPI_ENH_CTRLR0_CLK_STRETCH_EN	BIT(30)
+#define DW_SPI_ENH_CTRLR0_INST_DDR_EN		BIT(17)
+#define DW_SPI_ENH_CTRLR0_SPI_DDR_EN		BIT(16)
 #define DW_SPI_ENH_CTRLR0_WAIT_CYCLE_MASK	GENMASK(15, 11)
 #define DW_SPI_ENH_CTRLR0_INST_L_MASK		GENMASK(9, 8)
 #define DW_SPI_ENH_CTRLR0_INST_L_INST_L0	0x0
@@ -150,6 +153,9 @@
 #define DW_SPI_ENH_CTRLR0_TRANS_TYPE_TT2	0x2
 #define DW_SPI_ENH_CTRLR0_TRANS_TYPE_MASK	GENMASK(1, 0)

+/* Bit fields in DDR_DRIVE_EDGE */
+#define DW_SPI_DDR_DRIVE_EDGE_TDE_MASK		GENMASK(7, 0)
+
 /* Mem/DMA operations helpers */
 #define DW_SPI_WAIT_RETRIES			5
 #define DW_SPI_BUF_SIZE \
--
2.25.1

  reply	other threads:[~2026-10-22 10:26 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-22 10:26 [PATCH v1 0/4] Add DDR (Dual Data-Rate) support for DW SPI controller Changhuang Liang
2026-10-22 10:26 ` Changhuang Liang [this message]
2026-10-22 10:41   ` [PATCH v1 1/4] spi: dw: Add DDR (Dual Data-Rate) support for enhanced SPI mode Miquel Raynal
2026-10-22 10:26 ` [PATCH v1 2/4] mtd: spi-nor: Support mixed 1S-xD-xD DTR protocols in spi_nor_spimem_setup_op() Changhuang Liang
2026-10-22 10:45   ` Miquel Raynal
2026-10-22 12:19     ` Michael Walle
2026-10-22 10:26 ` [PATCH v1 3/4] mtd: spi-nor: debugfs: Correct DTR protocol names for 1S-xD-xD modes Changhuang Liang
2026-10-22 10:46   ` Miquel Raynal
2026-10-22 10:26 ` [PATCH v1 4/4] mtd: spi-nor: macronix: Add 1-4-4 DTR read support for MX66L1G45G Changhuang Liang
2026-10-22 10:47   ` Miquel Raynal
2026-10-22 12:23   ` Michael Walle

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261022102614.75696-2-changhuang.liang@starfivetech.com \
    --to=changhuang.liang@starfivetech.com \
    --cc=broonie@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mtd@lists.infradead.org \
    --cc=linux-spi@vger.kernel.org \
    --cc=miquel.raynal@bootlin.com \
    --cc=mwalle@kernel.org \
    --cc=pratyush@kernel.org \
    --cc=richard@nod.at \
    --cc=takahiro.kuwano@infineon.com \
    --cc=vigneshr@ti.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®