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 v2 2/5] spi: dw: Add DDR (Dual Data-Rate) support for enhanced SPI mode
Date: Wed, 28 Oct 2026 05:10:57 -0700 [thread overview]
Message-ID: <20261028121100.79667-3-changhuang.liang@starfivetech.com> (raw)
In-Reply-To: <20261028121100.79667-1-changhuang.liang@starfivetech.com>
Add support for 1S-xD-xD DDR transfers in the enhanced SPI mode of the
DW SPI controller, on controllers that have DW_SPI_CAP_DDR.
SPI_CTRLR0.SPI_DDR_EN switches the address, dummy and data phases to
DDR all together, and it only applies to the Dual/Quad/Octal frame
formats. So in supports_op() only accept DTR operations where the
command is sent in STR on one line, the data phase uses two lines or
more, and the address and dummy phases are DTR on the data lines.
DDR instruction phase (8D-8D-8D) is not supported for now.
For DDR transfers the dummy phase takes two bits per clock on each
line, so the wait cycles must be halved. Factor the wait cycle
calculation into dw_spi_enh_wait_cycles(), so that supports_op() and
exec_op() use the same value, and refuse dummy phases that are not a
whole number of clock cycles.
When DDR is enabled, program the DDR_DRIVE_EDGE register. The
recommended TDE value is SCKDV / 4, clamped to SCKDV / 2 - 1 and to
the field width. The register is only written when the controller
supports DDR.
Finally, advertise DTR to the spi-mem layer through a dedicated
dw_spi_ddr_mem_caps when the controller has DW_SPI_CAP_DDR.
Signed-off-by: Changhuang Liang <changhuang.liang@starfivetech.com>
---
drivers/spi/spi-dw-core.c | 84 +++++++++++++++++++++++++++++++++++----
drivers/spi/spi-dw.h | 4 ++
2 files changed, 81 insertions(+), 7 deletions(-)
diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c
index e8f70808352a..6b06b84c661b 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;
+ /* Address, dummy and data phases in DDR (SPI_CTRLR0.SPI_DDR_EN) */
+ bool addr_data_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->addr_data_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 TDE is SCKDV / 4, and it must not exceed
+ * SCKDV / 2 - 1. DDR_DRIVE_EDGE only exists if DDR is supported.
+ */
+ if (dws->caps & DW_SPI_CAP_DDR) {
+ if (enh_cfg->addr_data_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,49 @@ 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;
+}
+
+/*
+ * SPI_DDR_EN switches the address, dummy and data phases to DDR all
+ * together, and only in the Dual/Quad/Octal frame formats with the
+ * address sent on the data lines. DDR instruction phase is not
+ * supported, so only accept 1S-xD-xD operations.
+ */
+static bool dw_spi_supports_enh_ddr_op(const struct spi_mem_op *op)
+{
+ if (op->cmd.dtr || op->cmd.nbytes != 1 || op->cmd.buswidth != 1)
+ return false;
+
+ if (op->data.dir == SPI_MEM_NO_DATA || !op->data.dtr ||
+ op->data.buswidth < 2)
+ return false;
+
+ if (op->addr.nbytes &&
+ (!op->addr.dtr || op->addr.buswidth != op->data.buswidth))
+ return false;
+
+ /* WAIT_CYCLES counts full clock cycles. */
+ if (op->dummy.nbytes &&
+ (!op->dummy.dtr || !op->dummy.buswidth ||
+ (op->dummy.nbytes * BITS_PER_BYTE / op->dummy.buswidth) % 2))
+ return false;
+
+ return true;
+}
+
static bool dw_spi_supports_enh_mem_op(struct spi_mem *mem,
const struct spi_mem_op *op)
{
@@ -675,13 +736,14 @@ static bool dw_spi_supports_enh_mem_op(struct spi_mem *mem,
if (op->dummy.nbytes && !op->dummy.buswidth)
return false;
+ if ((op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr) &&
+ !dw_spi_supports_enh_ddr_op(op))
+ return false;
+
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 +1136,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);
+
+ /* All phases but the instruction share the DDR setting, see supports_op */
+ enh_cfg.addr_data_ddr = op->data.dtr;
dw_spi_enable_chip(dws, 0);
@@ -1389,6 +1453,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_ddr_mem_caps = {
+ .per_op_freq = true,
+ .dtr = true,
+};
+
int dw_spi_add_controller(struct device *dev, struct dw_spi *dws)
{
struct spi_controller *ctlr;
@@ -1445,7 +1514,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_DDR) ?
+ &dw_spi_ddr_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 f922e000e7a0..4c548fde89dd 100644
--- a/drivers/spi/spi-dw.h
+++ b/drivers/spi/spi-dw.h
@@ -67,6 +67,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
@@ -152,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
next prev parent reply other threads:[~2026-10-28 12:11 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-28 12:10 [PATCH v2 0/5] Add DDR (Dual Data-Rate) support for DW SPI controller Changhuang Liang
2026-10-28 12:10 ` [PATCH v2 1/5] spi: dw: Detect DDR support in enhanced SPI mode Changhuang Liang
2026-10-28 12:10 ` Changhuang Liang [this message]
2026-10-28 16:39 ` [PATCH v2 2/5] spi: dw: Add DDR (Dual Data-Rate) support for " Miquel Raynal
2026-10-28 12:10 ` [PATCH v2 3/5] mtd: spi-nor: Support mixed 1S-xD-xD DTR protocols in spi_nor_spimem_setup_op() Changhuang Liang
2026-10-28 12:10 ` [PATCH v2 4/5] mtd: spi-nor: debugfs: Correct DTR protocol names for 1S-xD-xD modes Changhuang Liang
2026-10-28 12:11 ` [PATCH v2 5/5] mtd: spi-nor: macronix: Add 1-4-4 DTR read support for MX66L1G45G Changhuang Liang
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=20261028121100.79667-3-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®