mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-03 03:31:41 +03:00
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:
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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, ®_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, ®_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
|
||||
|
||||
Reference in New Issue
Block a user