Skip to content

drivers: spi: esp32: DMA operations via GDMA driver #92796

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 137 additions & 28 deletions drivers/spi/spi_esp32_spim.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@ LOG_MODULE_REGISTER(esp32_spi, CONFIG_SPI_LOG_LEVEL);
#include <zephyr/drivers/spi.h>
#include <zephyr/drivers/interrupt_controller/intc_esp32.h>
#ifdef SOC_GDMA_SUPPORTED
#include <hal/gdma_hal.h>
#include <hal/gdma_ll.h>
#include <zephyr/drivers/dma.h>
#include <zephyr/drivers/dma/dma_esp32.h>
#endif
#include <zephyr/drivers/clock_control.h>
#include "spi_context.h"
#include "spi_esp32_spim.h"

#define SPI_DMA_MAX_BUFFER_SIZE 4092

#define SPI_DMA_RX 0
#define SPI_DMA_TX 1

static bool spi_esp32_transfer_ongoing(struct spi_esp32_data *data)
{
return spi_context_tx_on(&data->ctx) || spi_context_rx_on(&data->ctx);
Expand All @@ -47,8 +50,67 @@ static inline void spi_esp32_complete(const struct device *dev,
#ifdef CONFIG_SPI_ESP32_INTERRUPT
spi_context_complete(&data->ctx, dev, status);
#endif
}

#ifdef SOC_GDMA_SUPPORTED
static int spi_esp32_gdma_start(const struct device *dev, uint8_t dir, uint8_t *buf, size_t len)
{
const struct spi_esp32_config *cfg = dev->config;

struct dma_config dma_cfg = {};
struct dma_status dma_status = {};
struct dma_block_config dma_blk = {};
int err = 0;

uint8_t dma_channel = (dir == SPI_DMA_RX) ? cfg->dma_rx_ch : cfg->dma_tx_ch;

if (dma_channel == 0xFF) {
LOG_ERR("DMA channel is not configured in device tree");
return -EINVAL;
}

err = dma_get_status(cfg->dma_dev, dma_channel, &dma_status);
if (err) {
return -EINVAL;
}

if (dma_status.busy) {
LOG_ERR("DMA channel %d is busy", dma_channel);
return -EBUSY;
}

unsigned int key = irq_lock();

if (dir == SPI_DMA_RX) {
dma_cfg.channel_direction = PERIPHERAL_TO_MEMORY;
dma_blk.dest_address = (uint32_t)buf;
} else {
dma_cfg.channel_direction = MEMORY_TO_PERIPHERAL;
dma_blk.source_address = (uint32_t)buf;
}
dma_cfg.dma_slot = cfg->dma_host;
dma_cfg.block_count = 1;
dma_cfg.head_block = &dma_blk;
dma_blk.block_size = len;

err = dma_config(cfg->dma_dev, dma_channel, &dma_cfg);
if (err) {
LOG_ERR("Error configuring DMA (%d)", err);
goto unlock;
}

err = dma_start(cfg->dma_dev, dma_channel);
if (err) {
LOG_ERR("Error starting DMA (%d)", err);
goto unlock;
}

unlock:
irq_unlock(key);

return err;
}
#endif

static int IRAM_ATTR spi_esp32_transfer(const struct device *dev)
{
Expand All @@ -68,6 +130,8 @@ static int IRAM_ATTR spi_esp32_transfer(const struct device *dev)
uint8_t *tx_temp = NULL;
size_t dma_len_tx = MIN(ctx->tx_len * data->dfs, SPI_DMA_MAX_BUFFER_SIZE);
size_t dma_len_rx = MIN(ctx->rx_len * data->dfs, SPI_DMA_MAX_BUFFER_SIZE);
bool prepare_data = true;
int err = 0;

if (cfg->dma_enabled) {
/* bit_len needs to be at least one byte long when using DMA */
Expand All @@ -91,8 +155,8 @@ static int IRAM_ATTR spi_esp32_transfer(const struct device *dev)
rx_temp = k_calloc(((dma_len_rx << 3) + 31) / 8, sizeof(uint8_t));
if (!rx_temp) {
LOG_ERR("Error allocating temp buffer Rx");
k_free(tx_temp);
return -ENOMEM;
err = -ENOMEM;
goto free;
}
}
}
Expand All @@ -119,7 +183,38 @@ static int IRAM_ATTR spi_esp32_transfer(const struct device *dev)

/* configure SPI */
spi_hal_setup_trans(hal, hal_dev, hal_trans);
spi_hal_prepare_data(hal, hal_dev, hal_trans);

#if defined(SOC_GDMA_SUPPORTED)
if (cfg->dma_enabled && hal_trans->rcv_buffer && hal_trans->send_buffer) {
/* setup DMA channels via DMA driver */
spi_ll_dma_rx_fifo_reset(hal->hw);
spi_ll_infifo_full_clr(hal->hw);
spi_ll_dma_rx_enable(hal->hw, 1);

err = spi_esp32_gdma_start(dev, SPI_DMA_RX, hal_trans->rcv_buffer,
transfer_len_bytes);
if (err) {
goto free;
}

spi_ll_dma_tx_fifo_reset(hal->hw);
spi_ll_outfifo_empty_clr(hal->hw);
spi_ll_dma_tx_enable(hal->hw, 1);

err = spi_esp32_gdma_start(dev, SPI_DMA_TX, hal_trans->send_buffer,
transfer_len_bytes);
if (err) {
goto free;
}
}

prepare_data = !cfg->dma_enabled;
#endif

if (prepare_data) {
/* only for plain transfers or DMA transfers w/o GDMA */
spi_hal_prepare_data(hal, hal_dev, hal_trans);
}

/* send data */
spi_hal_user_start(hal);
Expand All @@ -129,19 +224,22 @@ static int IRAM_ATTR spi_esp32_transfer(const struct device *dev)
/* nop */
}

/* read data */
spi_hal_fetch_result(hal);
if (!cfg->dma_enabled) {
/* read data */
spi_hal_fetch_result(hal);
}

if (rx_temp) {
memcpy(&ctx->rx_buf[0], rx_temp, transfer_len_bytes);
}

spi_context_update_rx(&data->ctx, data->dfs, transfer_len_frames);

free:
k_free(tx_temp);
k_free(rx_temp);

return 0;
return err;
}

#ifdef CONFIG_SPI_ESP32_INTERRUPT
Expand All @@ -163,31 +261,24 @@ static int spi_esp32_init_dma(const struct device *dev)
{
const struct spi_esp32_config *cfg = dev->config;
struct spi_esp32_data *data = dev->data;
uint8_t channel_offset;

#ifdef SOC_GDMA_SUPPORTED
if (cfg->dma_dev) {
if (!device_is_ready(cfg->dma_dev)) {
LOG_ERR("DMA device is not ready");
return -ENODEV;
}
}

/* DMA operation won't be handled by HAL */
data->hal_config.dma_enabled = false;
#else
if (clock_control_on(cfg->clock_dev, (clock_control_subsys_t)cfg->dma_clk_src)) {
LOG_ERR("Could not enable DMA clock");
return -EIO;
}

#ifdef SOC_GDMA_SUPPORTED
gdma_hal_init(&data->hal_gdma, 0);
gdma_ll_enable_clock(data->hal_gdma.dev, true);
gdma_ll_tx_reset_channel(data->hal_gdma.dev, cfg->dma_host);
gdma_ll_rx_reset_channel(data->hal_gdma.dev, cfg->dma_host);
gdma_ll_tx_connect_to_periph(data->hal_gdma.dev, cfg->dma_host, GDMA_TRIG_PERIPH_SPI,
cfg->dma_host);
gdma_ll_rx_connect_to_periph(data->hal_gdma.dev, cfg->dma_host, GDMA_TRIG_PERIPH_SPI,
cfg->dma_host);
channel_offset = 0;
#else
channel_offset = 1;
#endif /* SOC_GDMA_SUPPORTED */
#ifdef CONFIG_SOC_SERIES_ESP32
/*Connect SPI and DMA*/
DPORT_SET_PERI_REG_BITS(DPORT_SPI_DMA_CHAN_SEL_REG, 3, cfg->dma_host + 1,
((cfg->dma_host + 1) * 2));
#endif /* CONFIG_SOC_SERIES_ESP32 */
int channel_offset = 1;

data->hal_config.dma_in = (spi_dma_dev_t *)cfg->spi;
data->hal_config.dma_out = (spi_dma_dev_t *)cfg->spi;
Expand All @@ -203,8 +294,16 @@ static int spi_esp32_init_dma(const struct device *dev)
k_free(data->hal_config.dmadesc_rx);
return -ENOMEM;
}
#endif /* SOC_GDMA_SUPPORTED */

#ifdef CONFIG_SOC_SERIES_ESP32
/* Connect SPI and DMA */
DPORT_SET_PERI_REG_BITS(DPORT_SPI_DMA_CHAN_SEL_REG, 3, cfg->dma_host + 1,
((cfg->dma_host + 1) * 2));
#endif /* CONFIG_SOC_SERIES_ESP32 */

spi_hal_init(&data->hal, cfg->dma_host + 1, &data->hal_config);

return 0;
}

Expand Down Expand Up @@ -511,6 +610,16 @@ static DEVICE_API(spi, spi_api) = {
#define GET_AS_CS(idx)
#endif

#if defined(SOC_GDMA_SUPPORTED)
#define SPI_DMA_CFG(idx) \
.dma_dev = ESP32_DT_INST_DMA_CTLR(idx, tx), \
.dma_tx_ch = ESP32_DT_INST_DMA_CELL(idx, tx, channel), \
.dma_rx_ch = ESP32_DT_INST_DMA_CELL(idx, rx, channel)
#else
#define SPI_DMA_CFG(idx) \
.dma_clk_src = DT_INST_PROP(idx, dma_clk)
#endif /* defined(SOC_GDMA_SUPPORTED) */

#define ESP32_SPI_INIT(idx) \
\
PINCTRL_DT_INST_DEFINE(idx); \
Expand Down Expand Up @@ -545,8 +654,8 @@ static DEVICE_API(spi, spi_api) = {
(clock_control_subsys_t)DT_INST_CLOCKS_CELL(idx, offset), \
.use_iomux = DT_INST_PROP(idx, use_iomux), \
.dma_enabled = DT_INST_PROP(idx, dma_enabled), \
.dma_clk_src = DT_INST_PROP(idx, dma_clk), \
.dma_host = DT_INST_PROP(idx, dma_host), \
SPI_DMA_CFG(idx), \
.cs_setup = DT_INST_PROP_OR(idx, cs_setup_time, 0), \
.cs_hold = DT_INST_PROP_OR(idx, cs_hold_time, 0), \
.line_idle_low = DT_INST_PROP(idx, line_idle_low), \
Expand Down
8 changes: 7 additions & 1 deletion drivers/spi/spi_esp32_spim.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,14 @@ struct spi_esp32_config {
clock_control_subsys_t clock_subsys;
bool use_iomux;
bool dma_enabled;
int dma_clk_src;
int dma_host;
#if defined(SOC_GDMA_SUPPORTED)
const struct device *dma_dev;
uint8_t dma_tx_ch;
uint8_t dma_rx_ch;
#else
int dma_clk_src;
#endif
int cs_setup;
int cs_hold;
bool line_idle_low;
Expand Down
1 change: 0 additions & 1 deletion dts/riscv/espressif/esp32c2/esp32c2_common.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,6 @@
interrupts = <SPI2_INTR_SOURCE IRQ_DEFAULT_PRIORITY 0>;
interrupt-parent = <&intc>;
clocks = <&clock ESP32_SPI2_MODULE>;
dma-clk = <ESP32_GDMA_MODULE>;
dma-host = <0>;
status = "disabled";
};
Expand Down
1 change: 0 additions & 1 deletion dts/riscv/espressif/esp32c3/esp32c3_common.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,6 @@
interrupts = <SPI2_INTR_SOURCE IRQ_DEFAULT_PRIORITY 0>;
interrupt-parent = <&intc>;
clocks = <&clock ESP32_SPI2_MODULE>;
dma-clk = <ESP32_GDMA_MODULE>;
dma-host = <0>;
status = "disabled";
};
Expand Down
1 change: 0 additions & 1 deletion dts/riscv/espressif/esp32c6/esp32c6_common.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,6 @@
interrupts = <GSPI2_INTR_SOURCE IRQ_DEFAULT_PRIORITY 0>;
interrupt-parent = <&intc>;
clocks = <&clock ESP32_SPI2_MODULE>;
dma-clk = <ESP32_GDMA_MODULE>;
dma-host = <0>;
status = "disabled";
};
Expand Down
2 changes: 0 additions & 2 deletions dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,6 @@
interrupts = <SPI2_INTR_SOURCE IRQ_DEFAULT_PRIORITY 0>;
interrupt-parent = <&intc>;
clocks = <&clock ESP32_SPI2_MODULE>;
dma-clk = <ESP32_GDMA_MODULE>;
dma-host = <0>;
status = "disabled";
};
Expand All @@ -346,7 +345,6 @@
interrupts = <SPI3_INTR_SOURCE IRQ_DEFAULT_PRIORITY 0>;
interrupt-parent = <&intc>;
clocks = <&clock ESP32_SPI3_MODULE>;
dma-clk = <ESP32_GDMA_MODULE>;
dma-host = <1>;
status = "disabled";
};
Expand Down
1 change: 1 addition & 0 deletions tests/drivers/spi/spi_loopback/socs/esp32c2.conf
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
CONFIG_SPI_ESP32_INTERRUPT=y
CONFIG_HEAP_MEM_POOL_SIZE=32768
CONFIG_DMA=y
7 changes: 7 additions & 0 deletions tests/drivers/spi/spi_loopback/socs/esp32c2.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,11 @@
pinctrl-0 = <&spim2_loopback>;
pinctrl-names = "default";
status = "okay";

dmas = <&dma 0>, <&dma 1>;
dma-names = "rx", "tx";
};

&dma {
status = "okay";
};
1 change: 1 addition & 0 deletions tests/drivers/spi/spi_loopback/socs/esp32c3.conf
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
CONFIG_SPI_ESP32_INTERRUPT=y
CONFIG_HEAP_MEM_POOL_SIZE=32768
CONFIG_DMA=y
7 changes: 7 additions & 0 deletions tests/drivers/spi/spi_loopback/socs/esp32c3.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,11 @@
pinctrl-0 = <&spim2_loopback>;
pinctrl-names = "default";
status = "okay";

dmas = <&dma 0>, <&dma 1>;
dma-names = "rx", "tx";
};

&dma {
status = "okay";
};
1 change: 1 addition & 0 deletions tests/drivers/spi/spi_loopback/socs/esp32c3_usb.conf
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
CONFIG_SPI_ESP32_INTERRUPT=y
CONFIG_HEAP_MEM_POOL_SIZE=32768
CONFIG_DMA=y
7 changes: 7 additions & 0 deletions tests/drivers/spi/spi_loopback/socs/esp32c3_usb.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,11 @@
pinctrl-0 = <&spim2_loopback>;
pinctrl-names = "default";
status = "okay";

dmas = <&dma 0>, <&dma 1>;
dma-names = "rx", "tx";
};

&dma {
status = "okay";
};
1 change: 1 addition & 0 deletions tests/drivers/spi/spi_loopback/socs/esp32c6_hpcore.conf
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
CONFIG_SPI_ESP32_INTERRUPT=y
CONFIG_HEAP_MEM_POOL_SIZE=32768
CONFIG_DMA=y
7 changes: 7 additions & 0 deletions tests/drivers/spi/spi_loopback/socs/esp32c6_hpcore.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,11 @@
pinctrl-0 = <&spim2_loopback>;
pinctrl-names = "default";
status = "okay";

dmas = <&dma 0>, <&dma 1>;
dma-names = "rx", "tx";
};

&dma {
status = "okay";
};
1 change: 1 addition & 0 deletions tests/drivers/spi/spi_loopback/socs/esp32s3_procpu.conf
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
CONFIG_SPI_ESP32_INTERRUPT=y
CONFIG_DMA=y
7 changes: 7 additions & 0 deletions tests/drivers/spi/spi_loopback/socs/esp32s3_procpu.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,11 @@
pinctrl-0 = <&spim3_loopback>;
pinctrl-names = "default";
status = "okay";

dmas = <&dma 0>, <&dma 1>;
dma-names = "rx", "tx";
};

&dma {
status = "okay";
};