drivers: adc: add IADC support for silabs series 2 boards
This commit introduces a new driver for the silabs Incremental ADC (IADC).
Signed-off-by: Martin Hoff <martin.hoff@silabs.com>
diff --git a/drivers/adc/CMakeLists.txt b/drivers/adc/CMakeLists.txt
index db3a7ae..f547561 100644
--- a/drivers/adc/CMakeLists.txt
+++ b/drivers/adc/CMakeLists.txt
@@ -46,6 +46,7 @@
zephyr_library_sources_ifdef(CONFIG_ADC_ESP32_DMA adc_esp32_dma.c)
zephyr_library_sources_ifdef(CONFIG_ADC_GECKO_ADC adc_gecko.c)
zephyr_library_sources_ifdef(CONFIG_ADC_GECKO_IADC iadc_gecko.c)
+zephyr_library_sources_ifdef(CONFIG_ADC_SILABS_IADC adc_silabs_iadc.c)
zephyr_library_sources_ifdef(CONFIG_ADC_SILABS_SIWX91X adc_silabs_siwx91x.c)
zephyr_library_sources_ifdef(CONFIG_ADC_INFINEON_CAT1 adc_ifx_cat1.c)
zephyr_library_sources_ifdef(CONFIG_ADC_SMARTBOND_GPADC adc_smartbond_gpadc.c)
diff --git a/drivers/adc/Kconfig b/drivers/adc/Kconfig
index b73d9f9..dfee7c2 100644
--- a/drivers/adc/Kconfig
+++ b/drivers/adc/Kconfig
@@ -136,6 +136,8 @@
source "drivers/adc/Kconfig.siwx91x"
+source "drivers/adc/Kconfig.silabs"
+
source "drivers/adc/Kconfig.ifx_cat1"
source "drivers/adc/Kconfig.smartbond"
diff --git a/drivers/adc/Kconfig.silabs b/drivers/adc/Kconfig.silabs
new file mode 100644
index 0000000..244edab
--- /dev/null
+++ b/drivers/adc/Kconfig.silabs
@@ -0,0 +1,11 @@
+# Copyright (c) 2025 Silicon Laboratories Inc.
+# SPDX-License-Identifier: Apache-2.0
+
+config ADC_SILABS_IADC
+ bool "Silabs Incremental ADC driver"
+ default y
+ depends on DT_HAS_SILABS_IADC_ENABLED
+ select SILABS_SISDK_IADC
+ select ADC_CONFIGURABLE_INPUTS
+ help
+ Enable the driver implementation for Silabs Incremental ADC
diff --git a/drivers/adc/adc_silabs_iadc.c b/drivers/adc/adc_silabs_iadc.c
new file mode 100644
index 0000000..81319f4
--- /dev/null
+++ b/drivers/adc/adc_silabs_iadc.c
@@ -0,0 +1,617 @@
+/*
+ * Copyright (c) 2025 Silicon Laboratories Inc.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#define DT_DRV_COMPAT silabs_iadc
+
+#include <zephyr/drivers/adc.h>
+#include <zephyr/drivers/clock_control.h>
+#include <zephyr/drivers/clock_control/clock_control_silabs.h>
+#include <zephyr/drivers/pinctrl.h>
+#include <zephyr/logging/log.h>
+#include <zephyr/pm/device.h>
+
+#include <sl_hal_iadc.h>
+
+LOG_MODULE_REGISTER(iadc, CONFIG_ADC_LOG_LEVEL);
+
+#define ADC_CONTEXT_USES_KERNEL_TIMER
+#include "adc_context.h"
+
+/* Comptibility section for IADC IP version*/
+#if (_IADC_IPVERSION_RESETVALUE == 0x00000000UL)
+
+#define IADC_NO_DIGAVG 1
+
+#define _IADC_CFG_DIGAVG_AVG1 -1
+#define _IADC_CFG_DIGAVG_AVG2 -1
+#define _IADC_CFG_DIGAVG_AVG4 -1
+#define _IADC_CFG_DIGAVG_AVG8 -1
+#define _IADC_CFG_DIGAVG_AVG16 -1
+
+#define IADC_NO_EXTENDED_ALIGN 1
+
+#define _IADC_SCANFIFOCFG_ALIGNMENT_RIGHT16 -1
+#define _IADC_SCANFIFOCFG_ALIGNMENT_RIGHT20 -1
+#define _IADC_SCANFIFOCFG_ALIGNMENT_LEFT16 -1
+#define _IADC_SCANFIFOCFG_ALIGNMENT_LEFT20 -1
+
+#define IADC_EXPLICIT_NEG_PIN 1
+
+#endif
+
+#define IADC_PORT_MASK 0xF0
+#define IADC_PIN_MASK 0x0F
+
+struct iadc_chan_conf {
+ sl_hal_iadc_analog_gain_t gain;
+ sl_hal_iadc_voltage_reference_t reference;
+ sl_hal_iadc_positive_port_input_t pos_port;
+ uint8_t pos_pin;
+ sl_hal_iadc_negative_port_input_t neg_port;
+ uint8_t neg_pin;
+ uint8_t iadc_conf_id;
+ bool initialized;
+};
+
+struct iadc_data {
+ const struct device *dev;
+ struct adc_context ctx;
+ struct iadc_chan_conf chan_conf[SL_HAL_IADC_CHANNEL_ID_MAX];
+ uint8_t adc_config_count; /* Number of ADC configs created (max 2) */
+ uint32_t clock_rate;
+ uint32_t channels;
+ uint16_t active_channels;
+ uint8_t alignment;
+ uint8_t oversampling;
+ uint8_t digital_averaging;
+ size_t data_size;
+ uint8_t *buffer;
+};
+
+struct iadc_config {
+ sl_hal_iadc_config_t config;
+ IADC_TypeDef *base;
+ const struct pinctrl_dev_config *pcfg;
+ const struct device *clock_dev;
+ struct silabs_clock_control_cmu_config clock_cfg;
+ void (*irq_cfg_func)(void);
+};
+
+static int iadc_find_or_create_adc_config(struct iadc_data *data, sl_hal_iadc_init_t *init,
+ const struct iadc_chan_conf *chan_conf)
+{
+ int iadc_conf_id;
+
+ /* Check if we can reuse existing ADC configs */
+ for (int i = 0; i < data->adc_config_count; i++) {
+ if (chan_conf->gain == init->configs[i].analog_gain &&
+ chan_conf->reference == init->configs[i].reference) {
+ return i;
+ }
+ }
+
+ if (data->adc_config_count >= ARRAY_SIZE(init->configs)) {
+ LOG_ERR("Maximum of 2 different ADC configs supported");
+ return -EINVAL;
+ }
+
+ iadc_conf_id = data->adc_config_count;
+ init->configs[iadc_conf_id].analog_gain = chan_conf->gain;
+ init->configs[iadc_conf_id].reference = chan_conf->reference;
+ data->adc_config_count++;
+
+ return iadc_conf_id;
+}
+
+static void iadc_configure_scan_table_entry(sl_hal_iadc_scan_table_entry_t *entry,
+ const struct iadc_chan_conf *chan_conf)
+{
+ *entry = (sl_hal_iadc_scan_table_entry_t){
+ .positive_port = chan_conf->pos_port,
+ .positive_pin = chan_conf->pos_pin,
+ .negative_port = chan_conf->neg_port,
+ .negative_pin = chan_conf->neg_pin,
+ .config_id = chan_conf->iadc_conf_id,
+ .include_in_scan = true,
+ };
+}
+
+/* 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.
+ */
+static int iadc_set_config(const struct device *dev)
+{
+ const struct iadc_config *config = dev->config;
+ IADC_TypeDef *iadc = config->base;
+ struct iadc_data *data = dev->data;
+ sl_hal_iadc_scan_table_t scan_table = {};
+ sl_hal_iadc_init_t adc_init_config = {
+ .configs[0].analog_gain = _IADC_CFG_ANALOGGAIN_ANAGAIN1,
+ .configs[1].analog_gain = _IADC_CFG_ANALOGGAIN_ANAGAIN1,
+ .configs[0].vref = SL_HAL_IADC_DEFAULT_VREF,
+ .configs[1].vref = SL_HAL_IADC_DEFAULT_VREF,
+ .configs[0].osr_high_speed = data->oversampling,
+ .configs[1].osr_high_speed = data->oversampling,
+#ifndef IADC_NO_DIGAVG
+ .configs[0].dig_avg = data->digital_averaging,
+ .configs[1].dig_avg = data->digital_averaging,
+#endif
+ };
+ sl_hal_iadc_init_scan_t scan_init = {
+ .data_valid_level = _IADC_SCANFIFOCFG_DVL_VALID4,
+ .alignment = data->alignment,
+ };
+ struct iadc_chan_conf *chan_conf;
+ uint32_t channels;
+ int res;
+
+ data->adc_config_count = 0;
+
+ channels = data->channels;
+
+ /*
+ * Process each channel configuration and set up ADC scan sequence.
+ * The IADC hardware supports only 2 different ADC configurations
+ * (gain + reference combinations + oversampling), so we need to map
+ * multiple channel configs to these 2 available ADC configs.
+ */
+ ARRAY_FOR_EACH(data->chan_conf, i) {
+ chan_conf = &data->chan_conf[i];
+
+ if (!chan_conf->initialized || (i != find_lsb_set(channels) - 1)) {
+ continue;
+ }
+
+ res = iadc_find_or_create_adc_config(data, &adc_init_config, chan_conf);
+ if (res < 0) {
+ LOG_DBG("IADC: too many different ADC configurations");
+ return res;
+ }
+
+ chan_conf->iadc_conf_id = res;
+
+ iadc_configure_scan_table_entry(&scan_table.entries[i], chan_conf);
+
+ channels &= ~BIT(i);
+ }
+
+ sl_hal_iadc_init(iadc, &adc_init_config, data->clock_rate);
+ sl_hal_iadc_init_scan(iadc, &scan_init, &scan_table);
+ sl_hal_iadc_set_scan_mask_multiple_entries(iadc, &scan_table);
+
+ return 0;
+}
+
+static int iadc_check_buffer_size(const struct adc_sequence *sequence, uint16_t active_channels,
+ size_t data_size)
+{
+ size_t needed_buffer_size = active_channels * data_size;
+
+ if (sequence->options) {
+ needed_buffer_size *= (1 + sequence->options->extra_samplings);
+ }
+
+ if (sequence->buffer_size < needed_buffer_size) {
+ LOG_DBG("Provided buffer is too small (%u/%u)", sequence->buffer_size,
+ needed_buffer_size);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+/*
+ * Goal of this function is to have concensius between wanted resolution, oversampling,
+ * the IADC Alignment Table, analog oversampling and digital averaging.
+ *
+ * Formulas:
+ * Output Resolution = 11 + log2(OversamplingRatio × DigitalAveraging)
+ * +------------+-------------+---------------+---------------+-------------+
+ * | Alignment | Oversample | Digital Avg | Num Samples | Output Res |
+ * | Setting | Ratio | | Averaged | |
+ * +------------+-------------+---------------+---------------+-------------+
+ * | 16-bit | 2x | 1x | 2 | 12 bits |
+ * | 16-bit | 8x | 2x | 16 | 15 bits |
+ * | 20-bit | 2x | 1x | 2 | 12 bits |
+ * | 20-bit | 16x | 4x | 64 | 17 bits |
+ * +------------+-------------+---------------+---------------+-------------+
+ */
+static int iadc_check_oversampling_and_resolution(const struct adc_sequence *sequence,
+ struct iadc_data *data)
+{
+ int res = sequence->resolution;
+ int ospl;
+
+ const static struct oversampling_table {
+ uint8_t analog_oversampling;
+ uint8_t digital_averaging;
+ } ospl_table[] = {
+ [0] = { _IADC_CFG_OSRHS_HISPD2, _IADC_CFG_DIGAVG_AVG1 }, /* 2x oversampling */
+ [1] = { _IADC_CFG_OSRHS_HISPD2, _IADC_CFG_DIGAVG_AVG1 }, /* 2x oversampling */
+ [2] = { _IADC_CFG_OSRHS_HISPD4, _IADC_CFG_DIGAVG_AVG1 }, /* 4x oversampling */
+ [3] = { _IADC_CFG_OSRHS_HISPD8, _IADC_CFG_DIGAVG_AVG1 }, /* 8x oversampling */
+ [4] = { _IADC_CFG_OSRHS_HISPD16, _IADC_CFG_DIGAVG_AVG1 }, /* 16x oversampling */
+ [5] = { _IADC_CFG_OSRHS_HISPD32, _IADC_CFG_DIGAVG_AVG1 }, /* 32x oversampling */
+ [6] = { _IADC_CFG_OSRHS_HISPD64, _IADC_CFG_DIGAVG_AVG1 }, /* 64x oversampling */
+ [7] = { _IADC_CFG_OSRHS_HISPD64, _IADC_CFG_DIGAVG_AVG2 }, /* 128x oversampling */
+ [8] = { _IADC_CFG_OSRHS_HISPD64, _IADC_CFG_DIGAVG_AVG4 }, /* 256x oversampling */
+ [9] = { _IADC_CFG_OSRHS_HISPD64, _IADC_CFG_DIGAVG_AVG8 }, /* 512x oversampling */
+ [10] = { _IADC_CFG_OSRHS_HISPD64, _IADC_CFG_DIGAVG_AVG16 }, /* 1024x oversampling */
+ };
+
+ if (!sequence->oversampling) {
+ ospl = 1;
+ } else {
+ ospl = sequence->oversampling;
+ }
+
+ if (ospl > ARRAY_SIZE(ospl_table) - 1) {
+ LOG_ERR("Unsupported oversampling %d", sequence->oversampling);
+ return -EINVAL;
+ }
+
+ if (ospl > 6 && IS_ENABLED(IADC_NO_DIGAVG)) {
+ LOG_ERR("Unsupported oversampling %d", ospl);
+ return -EINVAL;
+ }
+
+ if (res > 12 && IS_ENABLED(IADC_NO_EXTENDED_ALIGN)) {
+ LOG_ERR("Unsupported resolution %d", res);
+ return -EINVAL;
+ }
+
+ switch (res) {
+ case 12:
+ data->alignment = _IADC_SCANFIFOCFG_ALIGNMENT_RIGHT12;
+ break;
+ case 16:
+ data->alignment = _IADC_SCANFIFOCFG_ALIGNMENT_RIGHT16;
+ break;
+ case 20:
+ data->alignment = _IADC_SCANFIFOCFG_ALIGNMENT_RIGHT20;
+ break;
+ default:
+ LOG_ERR("Unsupported resolution %d", res);
+ return -EINVAL;
+ }
+
+ data->oversampling = ospl_table[ospl].analog_oversampling;
+ data->digital_averaging = ospl_table[ospl].digital_averaging;
+
+ return 0;
+}
+
+static int start_read(const struct device *dev, const struct adc_sequence *sequence)
+{
+ struct iadc_data *data = dev->data;
+ uint32_t channels;
+ uint16_t channel_count;
+ uint16_t index;
+ int res;
+
+ if (sequence->channels == 0) {
+ LOG_DBG("No channel requested");
+ return -EINVAL;
+ }
+
+ res = iadc_check_oversampling_and_resolution(sequence, data);
+ if (res < 0) {
+ return res;
+ }
+
+ if (data->alignment == _IADC_SCANFIFOCFG_ALIGNMENT_RIGHT20) {
+ data->data_size = sizeof(uint32_t);
+ } else {
+ data->data_size = sizeof(uint16_t);
+ }
+
+ if (sequence->calibrate) {
+ /* TODO: Implement runtime calibration */
+ LOG_DBG("Hardware have hardcoded calibration value but runtime calibration is not "
+ "supported");
+ }
+
+ channels = sequence->channels;
+ channel_count = 0;
+ while (channels) {
+ index = find_lsb_set(channels) - 1;
+ if (index >= SL_HAL_IADC_CHANNEL_ID_MAX) {
+ LOG_DBG("Requested channel index not available: %d", index);
+ return -EINVAL;
+ }
+
+ if (!data->chan_conf[index].initialized) {
+ LOG_DBG("Channel not initialized");
+ return -EINVAL;
+ }
+ channel_count++;
+ channels &= ~BIT(index);
+ }
+
+ res = iadc_check_buffer_size(sequence, channel_count, data->data_size);
+ if (res < 0) {
+ return res;
+ }
+
+ data->buffer = sequence->buffer;
+ data->active_channels = channel_count;
+
+ data->channels = sequence->channels;
+
+ res = iadc_set_config(data->dev);
+ if (res < 0) {
+ return res;
+ }
+
+ adc_context_start_read(&data->ctx, sequence);
+
+ res = adc_context_wait_for_completion(&data->ctx);
+
+ return res;
+}
+
+static void iadc_start_scan(const struct device *dev)
+{
+ const struct iadc_config *config = dev->config;
+ IADC_TypeDef *iadc = (IADC_TypeDef *)config->base;
+
+ sl_hal_iadc_enable_interrupts(iadc, IADC_IEN_SCANTABLEDONE);
+
+ sl_hal_iadc_start_scan(iadc);
+}
+
+static void adc_context_start_sampling(struct adc_context *ctx)
+{
+ struct iadc_data *data = CONTAINER_OF(ctx, struct iadc_data, ctx);
+
+ iadc_start_scan(data->dev);
+}
+
+static void adc_context_update_buffer_pointer(struct adc_context *ctx, bool repeat_sampling)
+{
+ struct iadc_data *data = CONTAINER_OF(ctx, struct iadc_data, ctx);
+
+ if (!repeat_sampling) {
+ data->buffer += data->active_channels * data->data_size;
+ }
+}
+
+static void iadc_isr(void *arg)
+{
+ const struct device *dev = (const struct device *)arg;
+ const struct iadc_config *config = dev->config;
+ struct iadc_data *data = dev->data;
+ uint8_t *sample_ptr = data->buffer;
+ IADC_TypeDef *iadc = config->base;
+ sl_hal_iadc_result_t sample;
+ uint32_t flags, err;
+
+ flags = sl_hal_iadc_get_pending_interrupts(iadc);
+ sl_hal_iadc_clear_interrupts(iadc, flags);
+
+ err = flags & (IADC_IF_PORTALLOCERR | IADC_IF_POLARITYERR | IADC_IF_EM23ABORTERROR |
+ IADC_IF_SCANFIFOOF | IADC_IF_SCANFIFOUF);
+
+ if (flags & IADC_IF_SCANTABLEDONE) {
+ while (sl_hal_iadc_get_scan_fifo_cnt(iadc) > 0) {
+ sample = sl_hal_iadc_pull_scan_fifo_result(iadc);
+ memcpy(sample_ptr, &sample.data, data->data_size);
+ sample_ptr += data->data_size;
+ }
+
+ adc_context_on_sampling_done(&data->ctx, dev);
+ }
+
+ if (err) {
+ LOG_ERR("IADC error, flags=%08x", err);
+ adc_context_complete(&data->ctx, -EIO);
+ }
+}
+
+static int iadc_read(const struct device *dev, const struct adc_sequence *sequence)
+{
+ struct iadc_data *data = dev->data;
+ int error;
+
+ adc_context_lock(&data->ctx, false, NULL);
+ error = start_read(dev, sequence);
+ adc_context_release(&data->ctx, error);
+
+ return error;
+}
+
+#ifdef CONFIG_ADC_ASYNC
+static int iadc_read_async(const struct device *dev, const struct adc_sequence *sequence,
+ struct k_poll_signal *async)
+{
+ struct iadc_data *data = dev->data;
+ int error;
+
+ adc_context_lock(&data->ctx, true, async);
+ error = start_read(dev, sequence);
+ adc_context_release(&data->ctx, error);
+
+ return error;
+}
+#endif
+
+static int iadc_channel_setup(const struct device *dev, const struct adc_channel_cfg *channel_cfg)
+{
+ struct iadc_data *data = dev->data;
+ struct iadc_chan_conf *chan_conf = NULL;
+
+ if (channel_cfg->channel_id < SL_HAL_IADC_CHANNEL_ID_MAX) {
+ chan_conf = &data->chan_conf[channel_cfg->channel_id];
+ } else {
+ LOG_DBG("Requested channel index not available: %d", channel_cfg->channel_id);
+ return -EINVAL;
+ }
+
+ if (channel_cfg->acquisition_time != ADC_ACQ_TIME_DEFAULT) {
+ LOG_ERR("Selected ADC acquisition time is not valid");
+ return -EINVAL;
+ }
+
+ chan_conf->initialized = false;
+
+ chan_conf->pos_port = (channel_cfg->input_positive & IADC_PORT_MASK) >> 4;
+ chan_conf->pos_pin = channel_cfg->input_positive & IADC_PIN_MASK;
+
+ if (channel_cfg->differential) {
+ chan_conf->neg_port = (channel_cfg->input_negative & IADC_PORT_MASK) >> 4;
+ chan_conf->neg_pin = channel_cfg->input_negative & IADC_PIN_MASK;
+ } else {
+ chan_conf->neg_port = _IADC_SCAN_PORTNEG_GND;
+ if (chan_conf->pos_port == _IADC_SCAN_PORTPOS_SUPPLY &&
+ IS_ENABLED(IADC_EXPLICIT_NEG_PIN)) {
+ chan_conf->neg_pin = 1;
+ }
+ }
+
+ switch (channel_cfg->gain) {
+ case ADC_GAIN_1_2:
+ chan_conf->gain = _IADC_CFG_ANALOGGAIN_ANAGAIN0P5;
+ break;
+ case ADC_GAIN_1:
+ chan_conf->gain = _IADC_CFG_ANALOGGAIN_ANAGAIN1;
+ break;
+ case ADC_GAIN_2:
+ chan_conf->gain = _IADC_CFG_ANALOGGAIN_ANAGAIN2;
+ break;
+ case ADC_GAIN_3:
+ chan_conf->gain = _IADC_CFG_ANALOGGAIN_ANAGAIN3;
+ break;
+ case ADC_GAIN_4:
+ chan_conf->gain = _IADC_CFG_ANALOGGAIN_ANAGAIN4;
+ break;
+ default:
+ LOG_ERR("unsupported channel gain '%d'", channel_cfg->gain);
+ return -EINVAL;
+ }
+
+ /* Setup reference */
+ switch (channel_cfg->reference) {
+ case ADC_REF_VDD_1:
+ chan_conf->reference = _IADC_CFG_REFSEL_VDDX;
+ break;
+ case ADC_REF_INTERNAL:
+ chan_conf->reference = _IADC_CFG_REFSEL_VBGR;
+ break;
+ case ADC_REF_EXTERNAL0:
+ chan_conf->reference = _IADC_CFG_REFSEL_VREF;
+ break;
+ default:
+ LOG_ERR("unsupported channel reference type '%d'", channel_cfg->reference);
+ return -EINVAL;
+ }
+
+ chan_conf->initialized = true;
+
+ LOG_DBG("Channel setup succeeded!");
+
+ return 0;
+}
+
+static int iadc_pm_action(const struct device *dev, enum pm_device_action action)
+{
+ const struct iadc_config *config = dev->config;
+ int err;
+
+ if (action == PM_DEVICE_ACTION_RESUME) {
+ err = clock_control_on(config->clock_dev,
+ (clock_control_subsys_t)&config->clock_cfg);
+ if (err < 0 && err != -EALREADY) {
+ return err;
+ }
+
+ err = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
+ if (err < 0 && err != -ENOENT) {
+ return err;
+ }
+ } else if (IS_ENABLED(CONFIG_PM_DEVICE) && (action == PM_DEVICE_ACTION_SUSPEND)) {
+ err = clock_control_off(config->clock_dev,
+ (clock_control_subsys_t)&config->clock_cfg);
+ if (err < 0) {
+ return err;
+ }
+
+ err = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_SLEEP);
+ if (err < 0 && err != -ENOENT) {
+ return err;
+ }
+ } else {
+ return -ENOTSUP;
+ }
+
+ return 0;
+}
+
+static int iadc_init(const struct device *dev)
+{
+ const struct iadc_config *config = dev->config;
+ struct iadc_data *data = dev->data;
+ int ret;
+
+ data->dev = dev;
+
+ ret = clock_control_on(config->clock_dev, (clock_control_subsys_t)&config->clock_cfg);
+ if (ret < 0 && ret != -EALREADY) {
+ return ret;
+ }
+
+ ret = clock_control_get_rate(config->clock_dev, (clock_control_subsys_t)&config->clock_cfg,
+ &data->clock_rate);
+ if (ret < 0) {
+ return ret;
+ }
+
+ config->irq_cfg_func();
+
+ adc_context_unlock_unconditionally(&data->ctx);
+
+ return pm_device_driver_init(dev, iadc_pm_action);
+}
+
+static DEVICE_API(adc, iadc_api) = {
+ .channel_setup = iadc_channel_setup,
+ .read = iadc_read,
+#ifdef CONFIG_ADC_ASYNC
+ .read_async = iadc_read_async,
+#endif
+ .ref_internal = SL_HAL_IADC_DEFAULT_VREF,
+};
+
+#define IADC_INIT(n) \
+ PINCTRL_DT_INST_DEFINE(n); \
+ PM_DEVICE_DT_INST_DEFINE(n, iadc_pm_action); \
+ \
+ static void iadc_config_func_##n(void); \
+ \
+ const static struct iadc_config iadc_config_##n = { \
+ .base = (IADC_TypeDef *)DT_INST_REG_ADDR(n), \
+ .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
+ .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \
+ .clock_cfg = SILABS_DT_INST_CLOCK_CFG(n), \
+ .irq_cfg_func = iadc_config_func_##n, \
+ }; \
+ \
+ static struct iadc_data iadc_data_##n = { \
+ ADC_CONTEXT_INIT_TIMER(iadc_data_##n, ctx), \
+ ADC_CONTEXT_INIT_LOCK(iadc_data_##n, ctx), \
+ ADC_CONTEXT_INIT_SYNC(iadc_data_##n, ctx), \
+ }; \
+ \
+ static void iadc_config_func_##n(void) \
+ { \
+ IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), iadc_isr, \
+ DEVICE_DT_INST_GET(n), 0); \
+ irq_enable(DT_INST_IRQN(n)); \
+ }; \
+ DEVICE_DT_INST_DEFINE(n, &iadc_init, PM_DEVICE_DT_INST_GET(n), &iadc_data_##n, \
+ &iadc_config_##n, POST_KERNEL, CONFIG_ADC_INIT_PRIORITY, &iadc_api);
+
+DT_INST_FOREACH_STATUS_OKAY(IADC_INIT)
diff --git a/dts/bindings/adc/silabs,iadc.yaml b/dts/bindings/adc/silabs,iadc.yaml
new file mode 100644
index 0000000..146b8cb
--- /dev/null
+++ b/dts/bindings/adc/silabs,iadc.yaml
@@ -0,0 +1,25 @@
+# Copyright (c) 2023 Antmicro <www.antmicro.com>
+# Copyright (c) 2025 Silicon Laboratories Inc.
+# SPDX-License-Identifier: Apache-2.0
+
+title: Silicon Labs Series 2 IADC
+
+description: |
+ Incremental ADC peripheral for Silicon Labs Series 2 SoCs.
+
+compatible: "silabs,iadc"
+
+include: [adc-controller.yaml, pinctrl-device.yaml]
+
+properties:
+ reg:
+ required: true
+
+ interrupts:
+ required: true
+
+ "#io-channel-cells":
+ const: 1
+
+io-channel-cells:
+ - input
diff --git a/modules/hal_silabs/simplicity_sdk/CMakeLists.txt b/modules/hal_silabs/simplicity_sdk/CMakeLists.txt
index 80296ef..4265b7a 100644
--- a/modules/hal_silabs/simplicity_sdk/CMakeLists.txt
+++ b/modules/hal_silabs/simplicity_sdk/CMakeLists.txt
@@ -216,7 +216,12 @@
)
endif()
-zephyr_library_sources_ifdef(CONFIG_SOC_GECKO_IADC ${EMLIB_DIR}/src/em_iadc.c)
+zephyr_library_sources_ifdef(CONFIG_SILABS_SISDK_SYSTEM ${PERIPHERAL_DIR}/src/sl_hal_system.c)
+
+#Keep em_iadc.c for compatibility for now. Not used anymore.
+zephyr_library_sources_ifdef(CONFIG_SILABS_SISDK_IADC ${EMLIB_DIR}/src/em_iadc.c)
+zephyr_library_sources_ifdef(CONFIG_SILABS_SISDK_IADC ${PERIPHERAL_DIR}/src/sl_hal_iadc.c)
+
zephyr_library_sources_ifdef(CONFIG_SOC_GECKO_BURTC ${EMLIB_DIR}/src/em_burtc.c)
zephyr_library_sources_ifdef(CONFIG_SOC_GECKO_CMU ${EMLIB_DIR}/src/em_cmu.c)
diff --git a/modules/hal_silabs/simplicity_sdk/Kconfig b/modules/hal_silabs/simplicity_sdk/Kconfig
index 2fcaa96..c1f13d8 100644
--- a/modules/hal_silabs/simplicity_sdk/Kconfig
+++ b/modules/hal_silabs/simplicity_sdk/Kconfig
@@ -7,9 +7,16 @@
config SILABS_SISDK_GPIO
bool "Peripheral HAL for GPIO"
+config SILABS_SISDK_SYSTEM
+ bool "Peripheral HAL for SYSTEM (device info)"
+
config SILABS_SISDK_I2C
bool "Peripheral HAL for I2C"
+config SILABS_SISDK_IADC
+ bool "Peripheral HAL for IADC"
+ select SILABS_SISDK_SYSTEM
+
config SILABS_SISDK_LETIMER
bool "Peripheral HAL for LETIMER"