Merge branch 'feat/spi_master_ddr_mode' into 'master'

feat(driver_spi): master driver support DDR(DTR) clock mode

Closes IDFGH-12948

See merge request espressif/esp-idf!49810
This commit is contained in:
morris
2026-08-20 17:40:32 +08:00
20 changed files with 178 additions and 8 deletions

View File

@@ -118,6 +118,7 @@ typedef struct {
#define SPI_TRANS_MULTILINE_ADDR SPI_TRANS_MODE_DIOQIO_ADDR ///< The data lines used at address phase is the same as data phase (otherwise, only one data line is used at address phase)
#define SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL (1<<11) ///< By default driver will automatically re-alloc dma buffer if it doesn't meet hardware alignment or dma_capable requirements, this flag is for you to disable this feature, you will need to take care of the alignment otherwise driver will return you error ESP_ERR_INVALID_ARG
#define SPI_TRANS_DMA_USE_PSRAM (1<<12) ///< Use PSRAM for DMA buffer directly, has speed limit, but no temp buffer and save memory
#define SPI_TRANS_DDRCLK (1<<13) ///< Use DDRCLK (double clock edge) for current transaction.
// Output flags
#define SPI_TRANS_DMA_RX_FAIL (1<<30) ///< RX transaction data lose flag, indicate DMA RX overflow

View File

@@ -649,6 +649,17 @@ int spi_get_actual_clock(int fapb, int hz, int duty_cycle)
return spi_hal_master_cal_clock(fapb, hz, duty_cycle);
}
static SPI_MASTER_ISR_ATTR bool s_spi_clock_need_reconfig(spi_device_t *dev, spi_trans_priv_t *trans_buf)
{
if (!trans_buf) {
return false;
}
if (dev->hal_dev.timing_conf.use_ddr_clk != !!(trans_buf->trans->flags & SPI_TRANS_DDRCLK)) {
return true;
}
return ((trans_buf->trans->override_freq_hz > 0) && (dev->hal_dev.timing_conf.expect_freq != trans_buf->trans->override_freq_hz));
}
// Setup the device-specified configuration registers. Called every time a new
// transaction is to be sent, but only apply new configurations when the device
// changes or timing change is required.
@@ -659,21 +670,31 @@ static SPI_MASTER_ISR_ATTR void spi_setup_device(spi_device_t *dev, spi_trans_pr
spi_hal_dev_config_t *hal_dev = &(dev->hal_dev);
bool clock_changed = false;
// check if timing config update is required
if (trans_buf && (trans_buf->trans->override_freq_hz > 0) && (hal_dev->timing_conf.expect_freq != trans_buf->trans->override_freq_hz)) {
if (s_spi_clock_need_reconfig(dev, trans_buf)) { // check if timing config update is required
const bool want_ddr = !!(trans_buf->trans->flags & SPI_TRANS_DDRCLK);
uint32_t target_freq = trans_buf->trans->override_freq_hz ? trans_buf->trans->override_freq_hz : dev->cfg.clock_speed_hz;
spi_hal_timing_param_t timing_param = {
.expected_freq = trans_buf->trans->override_freq_hz,
.use_ddr_clk = want_ddr,
.expected_freq = target_freq,
.clk_src_hz = dev->hal_dev.timing_conf.source_real_freq,
.duty_cycle = dev->cfg.duty_cycle_pos,
.input_delay_ns = dev->cfg.input_delay_ns,
.half_duplex = dev->hal_dev.half_duplex,
.no_compensate = dev->hal_dev.no_compensate,
.use_gpio = !(dev->host->bus_attr->flags & SPICOMMON_BUSFLAG_IOMUX_PINS),
};
if ((trans_buf->trans->override_freq_hz <= SPI_PERIPH_SRC_FREQ_MAX) && (ESP_OK == spi_hal_cal_clock_conf(&timing_param, &dev->hal_dev.timing_conf))) {
if ((target_freq <= SPI_PERIPH_SRC_FREQ_MAX) && (ESP_OK == spi_hal_cal_clock_conf(&timing_param, &dev->hal_dev.timing_conf))) {
clock_changed = true;
} else {
ESP_EARLY_LOGW(SPI_TAG, "assigned override_freq_hz %d not supported", trans_buf->trans->override_freq_hz);
// Keep previous timing_conf; clarify why reconfig failed.
if (want_ddr != !!dev->hal_dev.timing_conf.use_ddr_clk) {
ESP_EARLY_LOGW(SPI_TAG, "failed to switch to %s at %lu Hz, keep previous clock config",
want_ddr ? "DDR" : "SDR", (unsigned long)target_freq);
} else {
ESP_EARLY_LOGW(SPI_TAG, "failed to apply override_freq_hz %lu, keep previous frequency",
(unsigned long)target_freq);
}
}
}
@@ -1116,6 +1137,9 @@ static SPI_MASTER_ISR_ATTR esp_err_t check_trans_valid(spi_device_handle_t handl
SPI_CHECK(!(host->id == SPI3_HOST && trans_desc->flags & SPI_TRANS_MODE_OCT), "SPI3 does not support octal mode", ESP_ERR_INVALID_ARG);
SPI_CHECK(!((trans_desc->flags & SPI_TRANS_MODE_OCT) && (handle->cfg.flags & SPI_DEVICE_3WIRE)), "Incompatible when setting to both Octal mode and 3-wire-mode", ESP_ERR_INVALID_ARG);
SPI_CHECK(!((trans_desc->flags & SPI_TRANS_MODE_OCT) && !is_half_duplex), "Incompatible when setting to both Octal mode and half duplex mode", ESP_ERR_INVALID_ARG);
#endif
#if !SOC_SPI_SUPPORT_DDR_CLOCK
SPI_CHECK(!(trans_desc->flags & SPI_TRANS_DDRCLK), "DDRCLK is not supported on this chip", ESP_ERR_NOT_SUPPORTED);
#endif
SPI_CHECK(!((trans_desc->flags & (SPI_TRANS_MODE_DIO | SPI_TRANS_MODE_QIO)) && (handle->cfg.flags & SPI_DEVICE_3WIRE)), "Incompatible when setting to both multi-line mode and 3-wire-mode", ESP_ERR_INVALID_ARG);
SPI_CHECK(!((trans_desc->flags & (SPI_TRANS_MODE_DIO | SPI_TRANS_MODE_QIO)) && !is_half_duplex), "Incompatible when setting to both multi-line mode and half duplex mode", ESP_ERR_INVALID_ARG);

View File

@@ -21,6 +21,6 @@ endif()
# the component can be registered as WHOLE_ARCHIVE
idf_component_register(
SRCS ${srcs}
PRIV_REQUIRES esp_driver_spi spi_flash esp_timer esp_driver_gpio esp_mm
PRIV_REQUIRES esp_driver_spi spi_flash esp_timer esp_driver_gpio esp_mm esp_driver_uart
WHOLE_ARCHIVE
)

View File

@@ -10,6 +10,7 @@
#include "sdkconfig.h"
#include "driver/spi_master.h"
#include "driver/spi_slave.h"
#include "driver/uart.h"
#include "sys/param.h"
#include "driver/gpio.h"
#include "hal/spi_ll.h" // for SPI_LL_SRC_PRE_DIV_MAX
@@ -2192,3 +2193,60 @@ TEST_CASE("SPI_Master: PSRAM buffer transaction via EDMA", "[spi]")
spi_bus_free(TEST_SPI_HOST);
}
#endif
#if SOC_SPI_SUPPORT_DDR_CLOCK
TEST_CASE("Test master cmd/data DDR/SDR", "[spi]")
{
spi_bus_config_t buscfg = SPI_BUS_TEST_DEFAULT_CONFIG();
buscfg.miso_io_num = buscfg.mosi_io_num; // same pin to test data loopback
TEST_ESP_OK(spi_bus_initialize(TEST_SPI_HOST, &buscfg, SPI_DMA_DISABLED));
spi_device_handle_t dev0;
spi_device_interface_config_t devcfg = SPI_DEVICE_TEST_DEFAULT_CONFIG();
devcfg.command_bits = 16;
devcfg.address_bits = 16;
TEST_ESP_OK(spi_bus_add_device(TEST_SPI_HOST, &devcfg, &dev0));
// Tap SCLK with UART bitrate detection to count clock edges.
uart_bitrate_detect_config_t uart_cfg = {
.rx_io_num = buscfg.sclk_io_num,
.source_clk = UART_SCLK_DEFAULT,
};
spi_transaction_t trans_cfg = {
.cmd = 0x1234,
.addr = 0x5678,
.length = 32,
.tx_data = {0xAA, 0x34, 0x56, 0x5f},
};
uint32_t clk_edges[2];
for (int i = 0; i < 4; i++) {
const bool use_ddr = (i % 2) != 0;
printf("\nTest trans %s\n", use_ddr ? "DDR" : "SDR");
trans_cfg.flags = use_ddr ? SPI_TRANS_DDRCLK : 0;
trans_cfg.flags |= SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA;
trans_cfg.tx_data[3] *= 2;
memset(trans_cfg.rx_data, 0, sizeof(trans_cfg.rx_data));
TEST_ESP_OK(uart_detect_bitrate_start(UART_NUM_1, &uart_cfg));
TEST_ESP_OK(spi_device_polling_transmit(dev0, &trans_cfg));
uart_bitrate_res_t uart_res = {};
TEST_ESP_OK(uart_detect_bitrate_stop(UART_NUM_1, true, &uart_res));
int measured_freq_khz = (uart_res.clk_freq_hz / uart_res.pos_period) / 1000;
printf("clk edge %lu, measured freq %d kHz\n", uart_res.edge_cnt, measured_freq_khz);
clk_edges[i % 2] = uart_res.edge_cnt;
int bit_num = devcfg.command_bits + devcfg.address_bits + trans_cfg.length;
// SDR: one bit per clock period => 2 edges/bit; DDR: two bits per period => 1 edge/bit
TEST_ASSERT_INT_WITHIN(5, use_ddr ? bit_num : bit_num * 2, (int)uart_res.edge_cnt);
TEST_ASSERT_INT_WITHIN(devcfg.clock_speed_hz / 100000, measured_freq_khz, devcfg.clock_speed_hz / 1000);
ESP_LOG_BUFFER_HEX("Tx", trans_cfg.tx_data, 4);
ESP_LOG_BUFFER_HEX("Rx", trans_cfg.rx_data, 4);
TEST_ASSERT_EQUAL_HEX8_ARRAY(trans_cfg.tx_data, trans_cfg.rx_data, 4);
}
TEST_ASSERT_INT_WITHIN(50, clk_edges[1] * 2, clk_edges[0]); // DDR should cost half clk edges of SDR
TEST_ESP_OK(spi_bus_remove_device(dev0));
TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST));
}
#endif

View File

@@ -1068,6 +1068,17 @@ static inline void spi_ll_set_addr_bitlen(spi_dev_t *hw, int bitlen)
hw->user.usr_addr = bitlen ? 1 : 0;
}
/**
* Set the DDR mode for the SPI.
*
* @param hw Beginning address of the peripheral registers.
* @param enable True to enable DDR mode, false to disable.
*/
static inline void spi_ll_enable_ddr_mode(spi_dev_t *hw, bool enable)
{
hw->misc.clk_data_dtr_en = enable;
}
/**
* Set the address value in an intuitive way.
*

View File

@@ -1010,6 +1010,17 @@ static inline void spi_ll_set_addr_bitlen(spi_dev_t *hw, int bitlen)
hw->user.usr_addr = bitlen ? 1 : 0;
}
/**
* Set the DDR mode for the SPI.
*
* @param hw Beginning address of the peripheral registers.
* @param enable True to enable DDR mode, false to disable.
*/
static inline void spi_ll_enable_ddr_mode(spi_dev_t *hw, bool enable)
{
hw->misc.clk_data_dtr_en = enable;
}
/**
* Set the address value in an intuitive way.
*

View File

@@ -1011,6 +1011,17 @@ static inline void spi_ll_set_addr_bitlen(spi_dev_t *hw, int bitlen)
hw->user.usr_addr = bitlen ? 1 : 0;
}
/**
* Set the DDR mode for the SPI.
*
* @param hw Beginning address of the peripheral registers.
* @param enable True to enable DDR mode, false to disable.
*/
static inline void spi_ll_enable_ddr_mode(spi_dev_t *hw, bool enable)
{
hw->misc.clk_data_dtr_en = enable;
}
/**
* Set the address value in an intuitive way.
*

View File

@@ -1064,6 +1064,17 @@ static inline void spi_ll_set_addr_bitlen(spi_dev_t *hw, int bitlen)
hw->user.usr_addr = bitlen ? 1 : 0;
}
/**
* Set the DDR mode for the SPI.
*
* @param hw Beginning address of the peripheral registers.
* @param enable True to enable DDR mode, false to disable.
*/
static inline void spi_ll_enable_ddr_mode(spi_dev_t *hw, bool enable)
{
hw->misc.clk_data_dtr_en = enable;
}
/**
* Set the address value in an intuitive way.
*

View File

@@ -53,6 +53,7 @@ typedef struct {
* Left 0 if not known.
*/
bool use_gpio; ///< True if the GPIO matrix is used, otherwise false
bool use_ddr_clk; ///< Whether to use DDR mode for clock
} spi_hal_timing_param_t;
/**
@@ -70,6 +71,7 @@ typedef struct {
int timing_dummy; ///< Extra dummy needed to compensate the timing
int timing_miso_delay; ///< Extra miso delay clocks to compensate the timing
spi_sampling_point_t rx_sample_point;///< Sample data follow standard SPI timing in master mode
bool use_ddr_clk; ///< Whether to use DDR mode for clock
} spi_hal_timing_conf_t;
/**

View File

@@ -37,13 +37,18 @@ void spi_hal_setup_device(spi_hal_context_t *hal, const spi_hal_dev_config_t *de
spi_ll_master_set_cs_setup(hw, dev->cs_setup);
spi_ll_master_set_cs_hold(hw, dev->cs_hold);
spi_ll_master_select_cs(hw, dev->cs_pin_id);
#if SOC_SPI_SUPPORT_DDR_CLOCK
spi_ll_enable_ddr_mode(hw, dev->timing_conf.use_ddr_clk);
#endif
}
esp_err_t spi_hal_cal_clock_conf(const spi_hal_timing_param_t *timing_param, spi_hal_timing_conf_t *timing_conf)
{
spi_ll_clock_val_t reg_val;
int dummy, miso_delay;
int eff_clk_n = spi_ll_master_cal_clock(timing_param->clk_src_hz, timing_param->expected_freq, timing_param->duty_cycle, &reg_val);
// Hardware halves the output frequency when DDR is enabled; calculate with 2x so the effective rate stays the same.
uint32_t expected_freq = timing_param->use_ddr_clk ? timing_param->expected_freq * 2 : timing_param->expected_freq;
int eff_clk_n = spi_ll_master_cal_clock(timing_param->clk_src_hz, expected_freq, timing_param->duty_cycle, &reg_val);
//When the speed is too fast, we may need to use dummy cycles to compensate the reading.
//But these don't work for full-duplex connections.
@@ -63,8 +68,10 @@ esp_err_t spi_hal_cal_clock_conf(const spi_hal_timing_param_t *timing_param, spi
if (timing_conf) {
timing_conf->clock_reg = reg_val;
timing_conf->use_ddr_clk = timing_param->use_ddr_clk;
timing_conf->expect_freq = timing_param->expected_freq;
timing_conf->real_freq = eff_clk_n;
// eff_clk_n was calculated for 2x in DDR; report the effective data rate to the user.
timing_conf->real_freq = timing_param->use_ddr_clk ? eff_clk_n / 2 : eff_clk_n;
timing_conf->timing_dummy = dummy;
timing_conf->timing_miso_delay = miso_delay;
}

View File

@@ -1339,6 +1339,10 @@ config SOC_SPI_SUPPORT_OCT
bool
default y
config SOC_SPI_SUPPORT_DDR_CLOCK
bool
default y
config SOC_SPIRAM_XIP_SUPPORTED
bool
default y

View File

@@ -507,6 +507,7 @@
#define SOC_SPI_SUPPORT_SLEEP_RETENTION 1
#define SOC_SPI_SUPPORT_SLAVE_HD_VER2 1
#define SOC_SPI_SUPPORT_OCT 1
#define SOC_SPI_SUPPORT_DDR_CLOCK 1
/*-------------------------- SPIRAM CAPS ----------------------------------------*/
#define SOC_SPIRAM_XIP_SUPPORTED 1

View File

@@ -571,6 +571,10 @@ config SOC_SPI_SUPPORT_OCT
bool
default y
config SOC_SPI_SUPPORT_DDR_CLOCK
bool
default y
config SOC_MEMSPI_SUPPORT_CONTROL_DUMMY_OUT
bool
default y

View File

@@ -262,6 +262,7 @@
#define SOC_SPI_SUPPORT_SLAVE_HD_VER2 1
#define SOC_SPI_HD_BOTH_INOUT_SUPPORTED 1 //Support enabling MOSI and MISO phases together under Halfduplex mode
#define SOC_SPI_SUPPORT_OCT 1
#define SOC_SPI_SUPPORT_DDR_CLOCK 1
// Peripheral supports output given level during its "dummy phase"
// Only SPI1 supports this feature

View File

@@ -675,6 +675,10 @@ config SOC_SPI_SUPPORT_OCT
bool
default y
config SOC_SPI_SUPPORT_DDR_CLOCK
bool
default y
config SOC_SPIRAM_SUPPORTED
bool
default y

View File

@@ -282,6 +282,7 @@
#define SOC_SPI_MAXIMUM_BUFFER_SIZE 64
#define SOC_SPI_SUPPORT_SLAVE_HD_VER2 1
#define SOC_SPI_SUPPORT_OCT 1
#define SOC_SPI_SUPPORT_DDR_CLOCK 1
/*-------------------------- SPIRAM CAPS ----------------------------------------*/
#define SOC_SPIRAM_SUPPORTED 1

View File

@@ -891,6 +891,10 @@ config SOC_SPI_SUPPORT_OCT
bool
default y
config SOC_SPI_SUPPORT_DDR_CLOCK
bool
default y
config SOC_SPIRAM_XIP_SUPPORTED
bool
default y

View File

@@ -332,6 +332,7 @@
#define SOC_SPI_SUPPORT_SLEEP_RETENTION 1
#define SOC_SPI_SUPPORT_SLAVE_HD_VER2 1
#define SOC_SPI_SUPPORT_OCT 1
#define SOC_SPI_SUPPORT_DDR_CLOCK 1
/*-------------------------- SPIRAM CAPS ----------------------------------------*/
#define SOC_SPIRAM_XIP_SUPPORTED 1

View File

@@ -264,6 +264,13 @@ Supported line modes for {IDF_TARGET_NAME} are listed as follows, to make use of
SPI_TRANS_MULTILINE_CMD
- SPICOMMON_BUSFLAG_OCTAL
.. only:: SOC_SPI_SUPPORT_DDR_CLOCK
DDR Clock
^^^^^^^^^
Set :c:macro:`SPI_TRANS_DDRCLK` in :cpp:member:`spi_transaction_t::flags` to use double data rate mode for the current transaction. In this mode, the command, address, and data phases are sent/sampled on both the rising and falling edges of the clock. If this flag is not set, the transaction uses the traditional single data rate mode. DDRCLK mode is supported in 2/4/8 line modes as well.
Command and Address Phases
^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -264,6 +264,13 @@ SPI 总线传输事务由五个阶段构成,详见下表(任意阶段均可
SPI_TRANS_MULTILINE_CMD
- SPICOMMON_BUSFLAG_OCTAL
.. only:: SOC_SPI_SUPPORT_DDR_CLOCK
DDR 时钟
^^^^^^^^
:cpp:member:`spi_transaction_t::flags` 中设置 :c:macro:`SPI_TRANS_DDRCLK`,可使当前传输事务使用时钟双边沿数据模式,该模式下, cmd/addr/data 段都将在时钟的上升沿和下降沿都进行传输没有设置该标志时传输将回到传统单边沿数据模式。DDRCLK 模式在 2/4/8 线模式下同样支持。
命令阶段和地址阶段
^^^^^^^^^^^^^^^^^^^^^^^^^^