drivers: adc: add DMA support for Silabs IADC
This commit introduces DMA support for the Silabs IADC driver.
A new Kconfig option is added to enable DMA support, ensuring
compatibility with the existing ADC configuration.
DMA can be used for synch/asynch operation.
Signed-off-by: Martin Hoff <martin.hoff@silabs.com>
diff --git a/drivers/adc/Kconfig.silabs b/drivers/adc/Kconfig.silabs
index 244edab..2d954a3 100644
--- a/drivers/adc/Kconfig.silabs
+++ b/drivers/adc/Kconfig.silabs
@@ -9,3 +9,12 @@
select ADC_CONFIGURABLE_INPUTS
help
Enable the driver implementation for Silabs Incremental ADC
+
+config ADC_SILABS_IADC_DMA
+ bool "Silabs IADC async DMA support"
+ depends on ADC_SILABS_IADC
+ depends on DMA
+ help
+ Enable DMA support with the Silabs IADC driver.
+ This allows ADC conversions to be performed (asynchronously or not)
+ using DMA for improved performance.
diff --git a/drivers/adc/adc_silabs_iadc.c b/drivers/adc/adc_silabs_iadc.c
index 81319f4..fe731c2 100644
--- a/drivers/adc/adc_silabs_iadc.c
+++ b/drivers/adc/adc_silabs_iadc.c
@@ -9,6 +9,8 @@
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/clock_control_silabs.h>
+#include <zephyr/drivers/dma.h>
+#include <zephyr/drivers/dma/dma_silabs_ldma.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/logging/log.h>
#include <zephyr/pm/device.h>
@@ -45,6 +47,14 @@
#define IADC_PORT_MASK 0xF0
#define IADC_PIN_MASK 0x0F
+struct iadc_dma_channel {
+ const struct device *dma_dev;
+ struct dma_block_config blk_cfg;
+ struct dma_config dma_cfg;
+ int dma_channel;
+ bool enabled;
+};
+
struct iadc_chan_conf {
sl_hal_iadc_analog_gain_t gain;
sl_hal_iadc_voltage_reference_t reference;
@@ -60,6 +70,7 @@
const struct device *dev;
struct adc_context ctx;
struct iadc_chan_conf chan_conf[SL_HAL_IADC_CHANNEL_ID_MAX];
+ struct iadc_dma_channel dma;
uint8_t adc_config_count; /* Number of ADC configs created (max 2) */
uint32_t clock_rate;
uint32_t channels;
@@ -119,6 +130,104 @@
};
}
+#ifdef CONFIG_ADC_SILABS_IADC_DMA
+static int iadc_dma_init(const struct device *dev)
+{
+ const struct iadc_config *config = dev->config;
+ struct iadc_data *data = dev->data;
+ struct iadc_dma_channel *dma = &data->dma;
+
+ if (!dma->dma_dev) {
+ return 0;
+ }
+
+ if (!device_is_ready(dma->dma_dev)) {
+ LOG_ERR("DMA device not ready");
+ return -ENODEV;
+ }
+
+ dma->dma_channel = dma_request_channel(dma->dma_dev, NULL);
+ if (dma->dma_channel < 0) {
+ LOG_ERR("Failed to request DMA channel");
+ return -ENODEV;
+ }
+
+ memset(&dma->blk_cfg, 0, sizeof(dma->blk_cfg));
+ dma->blk_cfg.source_address = (uintptr_t)&(config->base)->SCANFIFODATA;
+ dma->blk_cfg.source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE;
+ dma->blk_cfg.dest_addr_adj = DMA_ADDR_ADJ_INCREMENT;
+ dma->dma_cfg.complete_callback_en = 1;
+ dma->dma_cfg.channel_priority = 3;
+ dma->dma_cfg.channel_direction = PERIPHERAL_TO_MEMORY;
+ dma->dma_cfg.head_block = &dma->blk_cfg;
+ dma->dma_cfg.user_data = data;
+
+ return 0;
+}
+
+static int iadc_dma_start(const struct device *dev)
+{
+ struct iadc_data *data = dev->data;
+ struct iadc_dma_channel *dma = &data->dma;
+ int ret;
+
+ if (!dma->dma_dev) {
+ return -ENODEV;
+ }
+
+ if (dma->enabled) {
+ return -EBUSY;
+ }
+
+ ret = dma_config(dma->dma_dev, dma->dma_channel, &dma->dma_cfg);
+ if (ret) {
+ LOG_ERR("DMA config error: %d", ret);
+ return ret;
+ }
+
+ dma->enabled = true;
+
+ ret = dma_start(dma->dma_dev, dma->dma_channel);
+ if (ret) {
+ LOG_ERR("DMA start error: %d", ret);
+ dma->enabled = false;
+ return ret;
+ }
+
+ return 0;
+}
+
+static void iadc_dma_stop(const struct device *dev)
+{
+ struct iadc_data *data = dev->data;
+ struct iadc_dma_channel *dma = &data->dma;
+
+ if (!dma->enabled) {
+ return;
+ }
+
+ dma_stop(dma->dma_dev, dma->dma_channel);
+
+ dma->enabled = false;
+}
+
+static void iadc_dma_cb(const struct device *dma_dev, void *user_data, uint32_t channel, int status)
+{
+ struct iadc_data *data = user_data;
+ const struct device *dev = data->dev;
+
+ if (status < 0) {
+ LOG_ERR("DMA transfer error: %d", status);
+ adc_context_complete(&data->ctx, status);
+ return;
+ }
+
+ iadc_dma_stop(dev);
+
+ adc_context_on_sampling_done(&data->ctx, dev);
+}
+#endif /* CONFIG_ADC_SILABS_IADC_DMA */
+
/* Oversampling and resolution are common for both ADC configs
* because they are not configurable per channel inside a ADC
* sequence and are common for a sequence.
@@ -149,8 +258,28 @@
uint32_t channels;
int res;
+ if (data->dma.dma_dev) {
+ scan_init.data_valid_level = _IADC_SCANFIFOCFG_DVL_VALID1;
+ /* Only needed to wake up DMA if EM is 2/3 */
+ scan_init.fifo_dma_wakeup = true;
+ }
+
data->adc_config_count = 0;
+ if (data->dma.dma_dev) {
+ if (data->alignment == _IADC_SCANFIFOCFG_ALIGNMENT_RIGHT20) {
+ data->dma.dma_cfg.source_data_size = 4;
+ data->dma.dma_cfg.dest_data_size = 4;
+ data->dma.dma_cfg.source_burst_length = 4;
+ data->dma.dma_cfg.dest_burst_length = 4;
+ } else {
+ data->dma.dma_cfg.source_data_size = 2;
+ data->dma.dma_cfg.dest_data_size = 2;
+ data->dma.dma_cfg.source_burst_length = 2;
+ data->dma.dma_cfg.dest_burst_length = 2;
+ }
+ }
+
channels = data->channels;
/*
@@ -340,6 +469,11 @@
data->buffer = sequence->buffer;
data->active_channels = channel_count;
+ if (data->dma.dma_dev) {
+ data->dma.blk_cfg.dest_address = (uintptr_t)data->buffer;
+ data->dma.blk_cfg.block_size = channel_count * data->data_size;
+ }
+
data->channels = sequence->channels;
res = iadc_set_config(data->dev);
@@ -357,9 +491,19 @@
static void iadc_start_scan(const struct device *dev)
{
const struct iadc_config *config = dev->config;
+ __maybe_unused struct iadc_data *data = dev->data;
IADC_TypeDef *iadc = (IADC_TypeDef *)config->base;
+#ifdef CONFIG_ADC_SILABS_IADC_DMA
+ if (data->dma.dma_dev) {
+ data->dma.blk_cfg.dest_address = (uintptr_t)data->buffer;
+ iadc_dma_start(dev);
+ } else {
+ sl_hal_iadc_enable_interrupts(iadc, IADC_IEN_SCANTABLEDONE);
+ }
+#else
sl_hal_iadc_enable_interrupts(iadc, IADC_IEN_SCANTABLEDONE);
+#endif
sl_hal_iadc_start_scan(iadc);
}
@@ -569,6 +713,13 @@
return ret;
}
+#ifdef CONFIG_ADC_SILABS_IADC_DMA
+ ret = iadc_dma_init(dev);
+ if (ret < 0) {
+ data->dma.dma_dev = NULL;
+ }
+#endif
+
config->irq_cfg_func();
adc_context_unlock_unconditionally(&data->ctx);
@@ -585,6 +736,17 @@
.ref_internal = SL_HAL_IADC_DEFAULT_VREF,
};
+#ifdef CONFIG_ADC_SILABS_IADC_DMA
+#define IADC_DMA_CHANNEL_INIT(n) \
+ .dma.dma_dev = DEVICE_DT_GET(DT_INST_DMAS_CTLR(n)), \
+ .dma.dma_cfg.dma_slot = SILABS_LDMA_REQSEL_TO_SLOT(DT_INST_DMAS_CELL_BY_IDX(n, 0, slot)), \
+ .dma.dma_cfg.dma_callback = iadc_dma_cb,
+#define IADC_DMA_CHANNEL(n) \
+ COND_CODE_1(DT_INST_NODE_HAS_PROP(n, dmas), (IADC_DMA_CHANNEL_INIT(n)), ())
+#else
+#define IADC_DMA_CHANNEL(n)
+#endif
+
#define IADC_INIT(n) \
PINCTRL_DT_INST_DEFINE(n); \
PM_DEVICE_DT_INST_DEFINE(n, iadc_pm_action); \
@@ -603,6 +765,7 @@
ADC_CONTEXT_INIT_TIMER(iadc_data_##n, ctx), \
ADC_CONTEXT_INIT_LOCK(iadc_data_##n, ctx), \
ADC_CONTEXT_INIT_SYNC(iadc_data_##n, ctx), \
+ IADC_DMA_CHANNEL(n) \
}; \
\
static void iadc_config_func_##n(void) \