[PATCH v1 1/4] spi: dw: Add DDR (Dual Data-Rate) support for enhanced SPI mode
Changhuang Liang
changhuang.liang at starfivetech.com
Thu Oct 22 03:26:11 PDT 2026
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 at 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
More information about the linux-mtd
mailing list