Merge branch 'feat/spi_add_data_output_inversion_v6.1' into 'release/v6.1'

feat(spi): support data output inversion (backport v6.1)

See merge request espressif/esp-idf!52745
This commit is contained in:
Jiang Jiang Jian
2026-09-15 20:37:14 +08:00
6 changed files with 86 additions and 10 deletions
@@ -65,6 +65,7 @@ extern "C"
#define SPICOMMON_BUSFLAG_OCTAL (SPICOMMON_BUSFLAG_QUAD|SPICOMMON_BUSFLAG_IO4_IO7) ///< Check existing of MOSI/MISO/WP/HD/SPIIO4/SPIIO5/SPIIO6/SPIIO7 pins as output. Or indicates bus able to work under octal mode.
#define SPICOMMON_BUSFLAG_NATIVE_PINS SPICOMMON_BUSFLAG_IOMUX_PINS
#define SPICOMMON_BUSFLAG_SLP_ALLOW_PD (1<<9) ///< Allow to power down the peripheral during light sleep, and auto recover then.
#define SPICOMMON_BUSFLAG_DATA_OUT_INV (1<<10) ///< Invert output data signals through the GPIO matrix.
/**
* @brief SPI DMA channels
@@ -94,6 +95,9 @@ typedef spi_common_dma_t spi_dma_chan_t;
* delay, which may cause incorrect read for >40MHz speeds.
*
* @note Be advised that the slave driver does not use the quadwp/quadhd lines and fields in spi_bus_config_t referring to these lines will be ignored and can thus safely be left uninitialized.
* @note When `SPICOMMON_BUSFLAG_DATA_OUT_INV` is set in `flags`, all configured bus signals are routed through the GPIO matrix.
* @note On ESP32, `data_io_default_level` only supports 0. When
`SPICOMMON_BUSFLAG_DATA_OUT_INV` is set, the idle data level is also inverted.
*/
typedef struct {
union {
@@ -622,7 +622,7 @@ static void s_spi_common_gpio_check_reserve(gpio_num_t gpio_num)
}
}
static void s_spi_common_bus_via_gpio(gpio_num_t gpio_num, int in_sig, int out_sig, uint64_t *io_mask)
static void s_spi_common_bus_via_gpio(gpio_num_t gpio_num, int in_sig, int out_sig, bool out_inv, uint64_t *io_mask)
{
assert(GPIO_IS_VALID_GPIO(gpio_num)); //coverity check
if (in_sig != -1) {
@@ -632,7 +632,7 @@ static void s_spi_common_bus_via_gpio(gpio_num_t gpio_num, int in_sig, int out_s
// For gpio_matrix, reserve output pins, see 'esp_gpio_reserve.h'
*io_mask |= BIT64(gpio_num);
s_spi_common_gpio_check_reserve(gpio_num);
gpio_matrix_output(gpio_num, out_sig, false, false);
gpio_matrix_output(gpio_num, out_sig, out_inv, false);
}
gpio_func_sel(gpio_num, PIN_FUNC_GPIO);
}
@@ -706,8 +706,11 @@ esp_err_t spicommon_bus_initialize_io(spi_host_device_t host, const spi_bus_conf
temp_flag |= SPICOMMON_BUSFLAG_DUAL;
}
bool data_out_inv = bus_config->flags & SPICOMMON_BUSFLAG_DATA_OUT_INV;
//check if the selected pins correspond to the iomux pins of the peripheral
bool use_iomux = !(flags & SPICOMMON_BUSFLAG_GPIO_PINS) && bus_uses_iomux_pins(host, bus_config);
bool use_iomux = !data_out_inv &&
!(flags & SPICOMMON_BUSFLAG_GPIO_PINS) &&
bus_uses_iomux_pins(host, bus_config);
if (use_iomux) {
temp_flag |= SPICOMMON_BUSFLAG_IOMUX_PINS;
} else {
@@ -717,6 +720,7 @@ esp_err_t spicommon_bus_initialize_io(spi_host_device_t host, const spi_bus_conf
uint32_t missing_flag = flags & ~temp_flag;
missing_flag &= ~SPICOMMON_BUSFLAG_MASTER; //don't check this flag
missing_flag &= ~SPICOMMON_BUSFLAG_SLP_ALLOW_PD;
missing_flag &= ~SPICOMMON_BUSFLAG_DATA_OUT_INV;
if (missing_flag != 0) {
//check pins existence
@@ -771,23 +775,25 @@ esp_err_t spicommon_bus_initialize_io(spi_host_device_t host, const spi_bus_conf
if (bus_config->mosi_io_num >= 0) {
int in_sig = spi_periph_signal[host].spid_in; // always connect input in case sio master is used
int out_sig = spi_periph_signal[host].spid_out;// always connect output in case sio slave is used, output capability is checked in slave hd driver
s_spi_common_bus_via_gpio(bus_config->mosi_io_num, in_sig, out_sig, &gpio_reserv);
s_spi_common_bus_via_gpio(bus_config->mosi_io_num, in_sig, out_sig, data_out_inv, &gpio_reserv);
}
if (bus_config->miso_io_num >= 0) {
int in_sig = ((flags & SPICOMMON_BUSFLAG_MASTER) || (temp_flag & SPICOMMON_BUSFLAG_DUAL)) ? spi_periph_signal[host].spiq_in : -1;
int out_sig = (!(flags & SPICOMMON_BUSFLAG_MASTER) || (temp_flag & SPICOMMON_BUSFLAG_DUAL)) ? spi_periph_signal[host].spiq_out : -1;
s_spi_common_bus_via_gpio(bus_config->miso_io_num, in_sig, out_sig, &gpio_reserv);
s_spi_common_bus_via_gpio(bus_config->miso_io_num, in_sig, out_sig, data_out_inv, &gpio_reserv);
}
if (bus_config->sclk_io_num >= 0) {
int in_sig = (flags & SPICOMMON_BUSFLAG_MASTER) ? -1 : spi_periph_signal[host].spiclk_in;
int out_sig = (flags & SPICOMMON_BUSFLAG_MASTER) ? spi_periph_signal[host].spiclk_out : -1;
s_spi_common_bus_via_gpio(bus_config->sclk_io_num, in_sig, out_sig, &gpio_reserv);
s_spi_common_bus_via_gpio(bus_config->sclk_io_num, in_sig, out_sig, false, &gpio_reserv);
}
if (bus_config->quadwp_io_num >= 0) {
s_spi_common_bus_via_gpio(bus_config->quadwp_io_num, spi_periph_signal[host].spiwp_in, spi_periph_signal[host].spiwp_out, &gpio_reserv);
s_spi_common_bus_via_gpio(bus_config->quadwp_io_num, spi_periph_signal[host].spiwp_in,
spi_periph_signal[host].spiwp_out, data_out_inv, &gpio_reserv);
}
if (bus_config->quadhd_io_num >= 0) {
s_spi_common_bus_via_gpio(bus_config->quadhd_io_num, spi_periph_signal[host].spihd_in, spi_periph_signal[host].spihd_out, &gpio_reserv);
s_spi_common_bus_via_gpio(bus_config->quadhd_io_num, spi_periph_signal[host].spihd_in,
spi_periph_signal[host].spihd_out, data_out_inv, &gpio_reserv);
}
#if SOC_SPI_SUPPORT_OCT
if (flags & SPICOMMON_BUSFLAG_OCTAL) {
@@ -800,7 +806,7 @@ esp_err_t spicommon_bus_initialize_io(spi_host_device_t host, const spi_bus_conf
};
for (size_t i = 0; i < sizeof(io_nums) / sizeof(io_nums[0]); i++) {
if (io_nums[i] >= 0) {
s_spi_common_bus_via_gpio(io_nums[i], io_signals[i][1], io_signals[i][0], &gpio_reserv);
s_spi_common_bus_via_gpio(io_nums[i], io_signals[i][1], io_signals[i][0], data_out_inv, &gpio_reserv);
}
}
}
@@ -331,7 +331,11 @@ static esp_err_t spi_master_init_driver(spi_host_device_t host_id)
}
spi_hal_init(&host->hal, host_id);
spi_hal_set_data_pin_idle_level(&host->hal, bus_attr->bus_cfg.data_io_default_level);
bool hal_idle_level = bus_attr->bus_cfg.data_io_default_level;
if (bus_attr->bus_cfg.flags & SPICOMMON_BUSFLAG_DATA_OUT_INV) {
hal_idle_level = !hal_idle_level; // Compensate for inversion set in GPIO Matrix
}
spi_hal_set_data_pin_idle_level(&host->hal, hal_idle_level);
if (host_id != SPI1_HOST) {
//SPI1 attributes are already initialized at start up.
@@ -19,6 +19,7 @@
#include "esp_memory_utils.h"
#include "esp_private/spi_common_internal.h"
#include "esp_private/esp_clk.h"
#include "esp_private/gpio.h"
#include "esp_private/sleep_cpu.h"
#include "esp_private/esp_sleep_internal.h"
#include "esp_private/esp_pmu.h"
@@ -697,6 +698,59 @@ TEST_CASE("spi bus setting with different pin configs", "[spi]")
#endif
}
TEST_CASE("spi data output inversion", "[spi]")
{
for (int invert = 0; invert < 2; invert++) {
ESP_LOGI(TAG, "Testing data output inversion: %s", invert ? "enabled" : "disabled");
spi_bus_config_t buscfg = SPI_BUS_TEST_DEFAULT_CONFIG();
buscfg.data_io_default_level = false;
buscfg.flags |= invert ? SPICOMMON_BUSFLAG_DATA_OUT_INV : 0;
spi_device_interface_config_t devcfg = SPI_DEVICE_TEST_DEFAULT_CONFIG();
spi_device_handle_t handle = NULL;
TEST_ESP_OK(spi_bus_initialize(TEST_SPI_HOST, &buscfg, SPI_DMA_DISABLED));
TEST_ESP_OK(spi_bus_add_device(TEST_SPI_HOST, &devcfg, &handle));
if (invert) {
const spi_bus_attr_t *bus_attr = spi_bus_get_attr(TEST_SPI_HOST);
TEST_ASSERT_TRUE(bus_attr->flags & SPICOMMON_BUSFLAG_GPIO_PINS);
}
/* Check inversion during a transaction. Add MOSI-to-MISO loopback after bus initialization to preserve the selected MOSI output route. */
TEST_ESP_OK(gpio_matrix_input(buscfg.mosi_io_num, spi_periph_signal[TEST_SPI_HOST].spiq_in, false));
uint8_t tx_data = 0xA5;
uint8_t rx_data = 0;
spi_transaction_t trans = {
.length = sizeof(tx_data) * 8,
.tx_buffer = &tx_data,
.rx_buffer = &rx_data,
};
TEST_ESP_OK(spi_device_polling_transmit(handle, &trans));
uint8_t expected_rx_data = invert ? 0x5A : 0xA5;
ESP_LOGI(TAG, "Loopback: TX=0x%02X, RX=0x%02X (expected=0x%02X)",
(unsigned)tx_data, (unsigned)rx_data, (unsigned)expected_rx_data);
TEST_ASSERT_EQUAL_HEX8(expected_rx_data, rx_data);
/* Check the MOSI idle level after the transaction. */
bool expected_idle_level = buscfg.data_io_default_level;
#if !SPI_LL_MOSI_FREE_LEVEL
if (invert) {
/* GPIO Matrix inversion also affects the idle level when the target cannot configure it. */
expected_idle_level = !buscfg.data_io_default_level;
}
#endif
int actual_idle_level = gpio_get_level(PIN_NUM_MOSI);
ESP_LOGI(TAG, "Idle: MOSI=%d (expected=%d)", actual_idle_level, expected_idle_level);
TEST_ASSERT_EQUAL_INT(expected_idle_level, actual_idle_level);
TEST_ESP_OK(spi_bus_remove_device(handle));
TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST));
}
}
TEST_CASE("SPI Master no response when switch from host1 (SPI2) to host2 (SPI3)", "[spi]")
{
//spi config