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
@@ -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
@@ -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);
@@ -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
)
@@ -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
@@ -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.
*
@@ -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.
*
@@ -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.
*
@@ -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.
*
@@ -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;
/**
+9 -2
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;
}
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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