summaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
Diffstat (limited to 'components')
-rwxr-xr-xcomponents/drivers/Kconfig1
-rw-r--r--components/drivers/adc/Kconfig121
-rw-r--r--components/drivers/adc/SConscript23
-rw-r--r--components/drivers/adc/adc.c (renamed from components/drivers/misc/adc.c)0
-rw-r--r--components/drivers/adc/adc_v2.c1779
-rw-r--r--components/drivers/adc/adc_v2_internal.h62
-rw-r--r--components/drivers/adc/adc_v2_msh.c1225
-rw-r--r--components/drivers/adc/adc_v2_trigger.c411
-rw-r--r--components/drivers/clock_time/Kconfig11
-rw-r--r--components/drivers/include/drivers/adc_v2.h622
-rw-r--r--components/drivers/include/drivers/adc_v2_trigger.h171
-rw-r--r--components/drivers/include/drivers/clock_time.h34
-rw-r--r--components/drivers/include/rtdevice.h6
-rw-r--r--components/drivers/misc/Kconfig8
-rw-r--r--components/drivers/misc/SConscript3
15 files changed, 4463 insertions, 14 deletions
diff --git a/components/drivers/Kconfig b/components/drivers/Kconfig
index db0334f42b..7ceb0720b4 100755
--- a/components/drivers/Kconfig
+++ b/components/drivers/Kconfig
@@ -5,6 +5,7 @@ rsource "ipc/Kconfig"
rsource "serial/Kconfig"
rsource "can/Kconfig"
+rsource "adc/Kconfig"
rsource "clock_time/Kconfig"
rsource "i2c/Kconfig"
rsource "phy/Kconfig"
diff --git a/components/drivers/adc/Kconfig b/components/drivers/adc/Kconfig
new file mode 100644
index 0000000000..28d9ec47a0
--- /dev/null
+++ b/components/drivers/adc/Kconfig
@@ -0,0 +1,121 @@
+menuconfig RT_USING_ADC
+ bool "Using ADC device drivers"
+ default n
+
+if RT_USING_ADC
+ config RT_USING_ADC_V2
+ bool "Using ADC V2 device drivers"
+ default n
+ help
+ Enable the standalone ADC V2 framework. ADC V2 uses the legacy ADC
+ public namespace, so it cannot be enabled together with ADC V1.
+
+ config RT_USING_ADC_V1
+ bool
+ default y if !RT_USING_ADC_V2
+
+if RT_USING_DM && RT_USING_ADC_V1
+ osource "$(SOC_DM_ADC_DIR)/Kconfig"
+endif
+
+if RT_USING_ADC_V2
+
+
+config RT_ADC_USING_TRIGGER
+ bool "Enable ADC trigger framework"
+ default n
+ help
+ Enable device-level ADC trigger requests, framework validation, caching,
+ and stream lifecycle coordination. Timer trigger frequency and TRGO
+ start/stop are delegated to the timer trigger backend.
+
+config RT_ADC_TRIGGER_USING_TIMER
+ bool "Enable ADC timer trigger selector support"
+ depends on RT_ADC_USING_TRIGGER
+ default n
+ help
+ Enable timer update/TRGO and timer compare events as ADC stream
+ trigger sources.
+
+config RT_ADC_TRIGGER_USING_COMPARE
+ bool "Enable ADC analog comparator trigger selector support"
+ depends on RT_ADC_USING_TRIGGER
+ default n
+ help
+ Enable analog comparator output events as ADC stream trigger sources.
+
+
+config RT_ADC_USING_STREAM
+ bool "Enable ADC stream framework"
+ default n
+ help
+ Enable long-running ADC stream sessions.
+
+ Disable this option to remove ADC stream runtime state, stream APIs,
+ and stream backend hooks from the ADC V2 framework.
+
+if RT_ADC_USING_STREAM
+
+menu "ADC stream buffering policies"
+
+config RT_ADC_STREAM_USING_LATEST
+ bool "Enable ADC stream latest-frame policy"
+ default y
+ help
+ Enable the latest-frame ADC stream policy.
+
+ This mode is intended for slow-changing or status-like ADC values, such
+ as battery voltage, temperature, potentiometer input, resistor-divider
+ voltage, or board health monitoring.
+
+ The DMA buffer stores exactly one complete ADC scan frame. Old values
+ may be overwritten. This mode does not require DMA half-transfer or
+ transfer-complete data interrupts, and it does not allocate a framework
+ FIFO.
+
+ This option may be enabled together with RT_ADC_STREAM_USING_FIFO. The
+ active policy is selected per stream at runtime.
+
+config RT_ADC_STREAM_USING_FIFO
+ bool "Enable ADC stream FIFO policy"
+ default n
+ help
+ Enable the FIFO ADC stream policy.
+
+ This mode is intended for continuous and loss-sensitive sampling. The
+ DMA buffer is used as a staging buffer, and completed DMA data events
+ are copied into a ringbuffer for ordered blocking reads.
+
+ FIFO mode requires DMA data events. The default DMA event mode uses both
+ half-transfer and transfer-complete events to reduce read latency. A
+ stream configuration may select full-only events to reduce interrupt
+ frequency, at the cost of higher latency and larger data bursts.
+
+ This option may be enabled together with RT_ADC_STREAM_USING_LATEST. The
+ active policy is selected per stream at runtime.
+
+comment "At least one ADC stream buffering policy should be enabled"
+ depends on !RT_ADC_STREAM_USING_LATEST && !RT_ADC_STREAM_USING_FIFO
+
+endmenu
+
+endif
+
+config RT_ADC_V2_USING_MSH
+ bool "Enable ADC V2 MSH command"
+ depends on RT_USING_FINSH
+ default y
+ help
+ Enable the ADC V2 MSH command.
+
+ This option exports the "adc" shell command for probing ADC devices,
+ configuring channels, reading raw samples, reading voltage values, and
+ reading one ADC sequence. When RT_ADC_USING_STREAM is enabled, stream
+ subcommands are also available.
+
+ Disable this option to remove the ADC V2 shell command while keeping
+ the ADC V2 framework and driver APIs available.
+
+endif
+
+endif
diff --git a/components/drivers/adc/SConscript b/components/drivers/adc/SConscript
new file mode 100644
index 0000000000..94101138f0
--- /dev/null
+++ b/components/drivers/adc/SConscript
@@ -0,0 +1,23 @@
+from building import *
+
+cwd = GetCurrentDir()
+src = []
+CPPPATH = [cwd + '/../include']
+group = []
+
+if GetDepend(['RT_USING_ADC']) and not GetDepend(['RT_USING_ADC_V2']):
+ src += ['adc.c']
+
+if GetDepend(['RT_USING_ADC_V2']):
+ src += ['adc_v2.c']
+
+if GetDepend(['RT_USING_ADC_V2', 'RT_ADC_USING_TRIGGER']):
+ src += ['adc_v2_trigger.c']
+
+if GetDepend(['RT_USING_ADC_V2', 'RT_USING_FINSH', 'RT_ADC_V2_USING_MSH']):
+ src += ['adc_v2_msh.c']
+
+if len(src):
+ group = DefineGroup('DeviceDrivers', src, depend = ['RT_USING_ADC'], CPPPATH = CPPPATH)
+
+Return('group')
diff --git a/components/drivers/misc/adc.c b/components/drivers/adc/adc.c
index 056c1f6c30..056c1f6c30 100644
--- a/components/drivers/misc/adc.c
+++ b/components/drivers/adc/adc.c
diff --git a/components/drivers/adc/adc_v2.c b/components/drivers/adc/adc_v2.c
new file mode 100644
index 0000000000..9ec3b6ac11
--- /dev/null
+++ b/components/drivers/adc/adc_v2.c
@@ -0,0 +1,1779 @@
+/*
+ * Copyright (c) 2006-2026, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-04-21 wdfk-prog add standalone adc v2 framework implementation
+ */
+
+/**
+ * @file adc_v2.c
+ * @brief Standalone ADC V2 framework implementation.
+ */
+
+#include <rtconfig.h>
+
+#if defined(RT_USING_ADC_V2)
+
+#include <rtdevice.h>
+#include <rtthread.h>
+
+#define DBG_TAG "adc.v2"
+#define DBG_LVL DBG_LOG
+#include <rtdbg.h>
+
+#include <drivers/adc_v2.h>
+#include "adc_v2_internal.h"
+
+/**
+ * @brief Get the current ADC framework state atomically.
+ * @param device Pointer to the ADC device object.
+ * @return Current ADC runtime state.
+ */
+enum rt_adc_state adc_get_state(rt_adc_device_t device)
+{
+ return (enum rt_adc_state)rt_atomic_load(&device->state);
+}
+
+#if DBG_LVL >= DBG_LOG
+/**
+ * @brief Convert one ADC framework state to a debug log string.
+ * @param state ADC runtime state.
+ * @return Constant state name string.
+ */
+static const char *adc_state_name(enum rt_adc_state state)
+{
+ switch (state)
+ {
+ case RT_ADC_STATE_IDLE:
+ return "IDLE";
+
+ case RT_ADC_STATE_LOCKED:
+ return "BUSY";
+
+#ifdef RT_ADC_USING_STREAM
+ case RT_ADC_STATE_STREAM:
+ return "STREAM";
+#endif /* RT_ADC_USING_STREAM */
+
+ default:
+ return "UNKNOWN";
+ }
+}
+
+/** @brief ADC control command string table offset base. */
+#define RT_ADC_CMD_NAME_BASE RT_ADC_CMD_GET_RESOLUTION
+
+/** @brief ADC control command string table entry count. */
+#define RT_ADC_CMD_NAME_COUNT (RT_ADC_CMD_CLEAR_CHANNEL_CONFIG - RT_ADC_CMD_GET_RESOLUTION + 1)
+
+/**
+ * @brief ADC control command name table.
+ */
+static const char * const rt_adc_control_cmd_names[RT_ADC_CMD_NAME_COUNT] =
+{
+ [RT_ADC_CMD_GET_RESOLUTION - RT_ADC_CMD_NAME_BASE] = "GET_RESOLUTION",
+ [RT_ADC_CMD_CALIBRATE - RT_ADC_CMD_NAME_BASE] = "CALIBRATE",
+ [RT_ADC_CMD_SET_VREF - RT_ADC_CMD_NAME_BASE] = "SET_VREF",
+ [RT_ADC_CMD_CALC_VREF - RT_ADC_CMD_NAME_BASE] = "CALC_VREF",
+ [RT_ADC_CMD_GET_VREF_CHANNEL - RT_ADC_CMD_NAME_BASE] = "GET_VREF_CHANNEL",
+ [RT_ADC_CMD_SET_SESSION - RT_ADC_CMD_NAME_BASE] = "SET_SESSION",
+ [RT_ADC_CMD_SET_CONFIG - RT_ADC_CMD_NAME_BASE] = "SET_CONFIG",
+ [RT_ADC_CMD_GET_CONFIG - RT_ADC_CMD_NAME_BASE] = "GET_CONFIG",
+ [RT_ADC_CMD_CLEAR_CHANNEL_CONFIG - RT_ADC_CMD_NAME_BASE] = "CLEAR_CHANNEL_CONFIG",
+};
+
+/**
+ * @brief Get ADC control command name.
+ * @param cmd ADC control command identifier.
+ * @return ADC control command name string.
+ */
+static const char *rt_adc_control_cmd_name(int cmd)
+{
+ int index;
+
+ index = cmd - RT_ADC_CMD_NAME_BASE;
+ if ((index < 0) || (index >= RT_ADC_CMD_NAME_COUNT) || (rt_adc_control_cmd_names[index] == RT_NULL))
+ {
+ return "UNKNOWN";
+ }
+
+ return rt_adc_control_cmd_names[index];
+}
+
+#ifdef RT_ADC_USING_STREAM
+/**
+ * @brief Convert one ADC stream policy to a debug log string.
+ * @param policy ADC stream buffering policy.
+ * @return Constant stream policy name string.
+ */
+static const char *adc_stream_policy_name(enum rt_adc_stream_policy policy)
+{
+ switch (policy)
+ {
+#ifdef RT_ADC_STREAM_USING_LATEST
+ case RT_ADC_STREAM_POLICY_LATEST:
+ return "LATEST";
+#endif /* RT_ADC_STREAM_USING_LATEST */
+
+#ifdef RT_ADC_STREAM_USING_FIFO
+ case RT_ADC_STREAM_POLICY_FIFO:
+ return "FIFO";
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+ default:
+ return "UNKNOWN";
+ }
+}
+#endif /* RT_ADC_USING_STREAM */
+
+#endif /* DBG_LVL >= DBG_LOG */
+
+/**
+ * @brief Store one ADC framework state atomically.
+ * @param device Pointer to the ADC device object.
+ * @param state New ADC runtime state.
+ */
+static void adc_set_state(rt_adc_device_t device, enum rt_adc_state state)
+{
+ LOG_D("state set: device=%s state=%s", device->parent.parent.name, adc_state_name(state));
+ rt_atomic_store(&device->state, (rt_atomic_t)state);
+}
+
+/**
+ * @brief Clear the cached active ADC session configuration.
+ * @param device Pointer to the ADC device object.
+ */
+static void adc_clear_session_ctrl(rt_adc_device_t device)
+{
+ rt_memset(&device->session_ctrl, 0, sizeof(device->session_ctrl));
+}
+
+/**
+ * @brief Cache one ADC session while the caller owns the device BUSY state.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @return Operation status.
+ */
+static rt_err_t adc_cache_session_ctrl(rt_adc_device_t device, rt_uint32_t channels)
+{
+ rt_size_t channel_count;
+
+ if ((device == RT_NULL) || (channels == 0U))
+ {
+ return -RT_EINVAL;
+ }
+
+ channel_count = rt_adc_channel_mask_count(channels);
+ if (channel_count == 0U)
+ {
+ return -RT_EINVAL;
+ }
+
+ device->session_ctrl.channels = channels;
+ device->session_ctrl.channel_count = channel_count;
+ device->session_ctrl.configured = RT_TRUE;
+
+ return RT_EOK;
+}
+
+/**
+ * @brief Try to change the ADC framework state atomically.
+ * @param device Pointer to the ADC device object.
+ * @param expected Expected current ADC runtime state.
+ * @param target Target ADC runtime state.
+ * @return RT_TRUE if the state transition succeeded.
+ */
+static rt_bool_t adc_try_set_state(rt_adc_device_t device, enum rt_adc_state expected, enum rt_adc_state target)
+{
+ rt_atomic_t old_state;
+ rt_bool_t matched;
+
+ old_state = (rt_atomic_t)expected;
+ matched = (rt_atomic_compare_exchange_strong(&device->state, &old_state, (rt_atomic_t)target) != 0) ? RT_TRUE : RT_FALSE;
+ if (matched == RT_TRUE)
+ {
+ LOG_D("state transition: device=%s %s -> %s", device->parent.parent.name, adc_state_name(expected), adc_state_name(target));
+ }
+ else
+ {
+ LOG_W("state transition rejected: device=%s expected=%d actual=%d target=%d", device->parent.parent.name, (int)expected, (int)old_state, (int)target);
+ }
+
+ return matched;
+}
+
+/**
+ * @brief Apply one ADC conversion session to the backend driver.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @return Operation status.
+ */
+static rt_err_t adc_session_config(rt_adc_device_t device, rt_uint32_t channels)
+{
+ rt_size_t channel_count;
+ rt_err_t result;
+
+ if ((device == RT_NULL) || (channels == 0U) ||
+ (device->ops == RT_NULL) || (device->ops->core == RT_NULL) ||
+ (device->ops->core->session_config == RT_NULL))
+ {
+ return -RT_EINVAL;
+ }
+
+ channel_count = rt_adc_channel_mask_count(channels);
+ LOG_D("session config: device=%s channels=0x%08x count=%d",
+ device->parent.parent.name, channels, channel_count);
+
+ result = device->ops->core->session_config(device, channels);
+ if (result != RT_EOK)
+ {
+ goto fail;
+ }
+
+ result = adc_cache_session_ctrl(device, channels);
+ if (result != RT_EOK)
+ {
+ goto fail;
+ }
+
+ LOG_D("session configure done: device=%s", device->parent.parent.name);
+ return RT_EOK;
+
+fail:
+ adc_clear_session_ctrl(device);
+ LOG_E("session configure failed: device=%s result=%d", device->parent.parent.name, result);
+
+ return result;
+}
+
+/**
+ * @brief Get the raw sample index of one channel in a configured session.
+ * @param session_channels Configured ADC session channel mask.
+ * @param channel Single-channel mask to locate.
+ * @param index Pointer to the output raw sample index.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_session_channel_index(rt_uint32_t session_channels, rt_uint32_t channel, rt_size_t *index)
+{
+ if ((index == RT_NULL) || (session_channels == 0U) || (rt_adc_channel_mask_count(channel) != 1U) || ((session_channels & channel) == 0U))
+ {
+ return -RT_EINVAL;
+ }
+
+ *index = rt_adc_channel_mask_count(session_channels & (channel - 1U));
+ return RT_EOK;
+}
+
+/**
+ * @brief Transfer ADC sequence samples after the session has been selected.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the sequence request object.
+ * @param sample_total Number of samples to read.
+ * @param read_count Pointer to the stored sample count.
+ * @return Operation status.
+ */
+static rt_err_t adc_sequence_transfer_locked(rt_adc_device_t device, rt_uint32_t channels,
+ const struct rt_adc_sequence_cfg *cfg, rt_size_t sample_total, rt_size_t *read_count)
+{
+ rt_err_t result;
+ rt_err_t stop_result;
+ rt_size_t index;
+
+ *read_count = 0U;
+ result = device->ops->sequence->start(device, channels, cfg);
+ if (result != RT_EOK)
+ {
+ LOG_E("sequence start failed: device=%s result=%d", device->parent.parent.name, result);
+ return result;
+ }
+
+ for (index = 0U; index < sample_total; index++)
+ {
+ result = device->ops->sequence->read(device, &cfg->buffer[index], cfg->timeout_ms);
+ if (result != RT_EOK)
+ {
+ LOG_E("sequence read failed: device=%s index=%u result=%d",
+ device->parent.parent.name, (unsigned int)index, result);
+ break;
+ }
+
+ (*read_count)++;
+ }
+
+ stop_result = device->ops->sequence->stop(device);
+ if (stop_result != RT_EOK)
+ {
+ LOG_E("sequence stop failed: device=%s result=%d", device->parent.parent.name, stop_result);
+ return (result != RT_EOK) ? result : stop_result;
+ }
+
+ return result;
+}
+
+/**
+ * @brief Open the ADC device.
+ * @param dev Pointer to the RT-Thread device object.
+ * @param oflag Open flags.
+ * @return Operation status.
+ */
+static rt_err_t adc_open_device(rt_device_t dev, rt_uint16_t oflag)
+{
+ rt_adc_device_t device;
+ rt_err_t result;
+
+ RT_UNUSED(oflag);
+
+ if (dev == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ device = (rt_adc_device_t)dev;
+ if ((device->ops == RT_NULL) || (device->ops->core == RT_NULL) ||
+ (device->ops->core->open == RT_NULL))
+ {
+ return -RT_ENOSYS;
+ }
+
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+
+ LOG_D("open start: device=%s", device->parent.parent.name);
+ result = device->ops->core->open(device);
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ if (result == RT_EOK)
+ {
+ LOG_I("open done: device=%s", device->parent.parent.name);
+ }
+ else
+ {
+ LOG_E("open failed: device=%s result=%d", device->parent.parent.name, result);
+ }
+ return result;
+}
+
+/**
+ * @brief Close the ADC device.
+ * @param dev Pointer to the RT-Thread device object.
+ * @return Operation status.
+ */
+static rt_err_t adc_close_device(rt_device_t dev)
+{
+ rt_adc_device_t device;
+ rt_err_t result;
+
+ if (dev == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ device = (rt_adc_device_t)dev;
+ if ((device->ops == RT_NULL) || (device->ops->core == RT_NULL) || (device->ops->core->close == RT_NULL))
+ {
+ return -RT_ENOSYS;
+ }
+
+#ifdef RT_ADC_USING_STREAM
+ enum rt_adc_state state = adc_get_state(device);
+ if (state == RT_ADC_STATE_STREAM)
+ {
+ result = rt_adc_stream_stop(device);
+ if (result != RT_EOK)
+ {
+ LOG_E("stop stream before close failed: device=%s result=%d", device->parent.parent.name, result);
+ return result;
+ }
+ }
+ else if (state != RT_ADC_STATE_IDLE)
+ {
+ return -RT_EBUSY;
+ }
+#endif /* RT_ADC_USING_STREAM */
+
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+
+ LOG_D("close start: device=%s", device->parent.parent.name);
+ result = device->ops->core->close(device);
+ if (result == RT_EOK)
+ {
+ adc_clear_session_ctrl(device);
+ }
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ if (result == RT_EOK)
+ {
+ LOG_I("close done: device=%s", device->parent.parent.name);
+ }
+ else
+ {
+ LOG_E("close failed: device=%s result=%d", device->parent.parent.name, result);
+ }
+ return result;
+}
+
+/**
+ * @brief Read ADC raw samples from the configured session.
+ * @param dev Pointer to the RT-Thread device object.
+ * @param pos Unused read position.
+ * @param buffer Pointer to the output sample buffer.
+ * @param size Output sample buffer length in samples.
+ * @return Number of samples read, or negative error code on failure.
+ *
+ * @note @p size must match the configured session channel count. The output
+ * order follows the ascending bit order of the configured channel mask.
+ */
+static rt_ssize_t adc_read_device(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
+{
+ rt_adc_device_t device;
+ rt_uint32_t *values;
+ rt_err_t result;
+
+ RT_UNUSED(pos);
+
+ if ((dev == RT_NULL) || (buffer == RT_NULL))
+ {
+ return -RT_EINVAL;
+ }
+
+ device = (rt_adc_device_t)dev;
+ values = (rt_uint32_t *)buffer;
+
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+
+ if ((device->session_ctrl.configured != RT_TRUE) || (device->session_ctrl.channel_count == 0U) ||
+ (size != device->session_ctrl.channel_count))
+ {
+ result = -RT_EINVAL;
+ goto out;
+ }
+
+ struct rt_adc_sequence_cfg sequence_cfg;
+ rt_size_t read_count;
+
+ rt_memset(&sequence_cfg, 0, sizeof(sequence_cfg));
+ sequence_cfg.buffer = values;
+ sequence_cfg.buffer_length = size;
+ sequence_cfg.timeout_ms = -1;
+
+ result = adc_sequence_transfer_locked(device, device->session_ctrl.channels,
+ &sequence_cfg, size, &read_count);
+ if ((result == RT_EOK) && (read_count != size))
+ {
+ result = -RT_ERROR;
+ }
+
+out:
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+
+ return (result == RT_EOK) ? (rt_ssize_t)size : (rt_ssize_t)result;
+}
+
+/**
+ * @brief Handle ADC control commands.
+ * @param dev Pointer to the RT-Thread device object.
+ * @param cmd Control command.
+ * @param args Pointer to the control argument buffer.
+ * @return Operation status.
+ */
+static rt_err_t adc_control_device(rt_device_t dev, int cmd, void *args)
+{
+ rt_adc_device_t device;
+ rt_err_t result;
+
+ if (dev == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ device = (rt_adc_device_t)dev;
+ LOG_D("control request: device=%s cmd=%s(0x%x)", device->parent.parent.name, rt_adc_control_cmd_name(cmd), cmd);
+ switch (cmd)
+ {
+ case RT_ADC_CMD_GET_RESOLUTION:
+ case RT_ADC_CMD_GET_VREF_CHANNEL:
+ case RT_ADC_CMD_CALC_VREF:
+ case RT_ADC_CMD_CALIBRATE:
+ case RT_ADC_CMD_SET_CONFIG:
+ case RT_ADC_CMD_GET_CONFIG:
+ if ((device->ops == RT_NULL) || (device->ops->core == RT_NULL) ||
+ (device->ops->core->control == RT_NULL))
+ {
+ return -RT_ENOSYS;
+ }
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+ result = device->ops->core->control(device, cmd, args);
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ return result;
+
+ case RT_ADC_CMD_CLEAR_CHANNEL_CONFIG:
+ if (args != RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ if ((dev->open_flag & RT_DEVICE_OFLAG_OPEN) == 0U)
+ {
+ return -RT_EIO;
+ }
+
+ if ((device->ops == RT_NULL) || (device->ops->core == RT_NULL) ||
+ (device->ops->core->control == RT_NULL))
+ {
+ return -RT_ENOSYS;
+ }
+
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+
+ result = device->ops->core->control(device, cmd, RT_NULL);
+ if (result == RT_EOK)
+ {
+ adc_clear_session_ctrl(device);
+ }
+
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ return result;
+
+ case RT_ADC_CMD_SET_VREF:
+ if (args == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ rt_uint32_t vref_mv = *((rt_uint32_t *)args);
+ if (vref_mv == 0U)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+ device->default_vref_mv = vref_mv;
+ LOG_I("fallback vref updated: %lu mV", (unsigned long)device->default_vref_mv);
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ return RT_EOK;
+
+ case RT_ADC_CMD_SET_SESSION:
+ if (args == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+ result = adc_session_config(device, *((const rt_uint32_t *)args));
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ return result;
+
+ default:
+ if ((device->ops == RT_NULL) || (device->ops->core == RT_NULL) ||
+ (device->ops->core->control == RT_NULL))
+ {
+ return -RT_ENOSYS;
+ }
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+ result = device->ops->core->control(device, cmd, args);
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ return result;
+ }
+}
+
+#ifdef RT_USING_DEVICE_OPS
+/**
+ * @brief ADC device-framework operation table.
+ */
+static const struct rt_device_ops adc_device_ops = {
+ RT_NULL,
+ adc_open_device,
+ adc_close_device,
+ adc_read_device,
+ RT_NULL,
+ adc_control_device,
+};
+#endif /* RT_USING_DEVICE_OPS */
+
+/**
+ * @brief Register an ADC device.
+ * @param device Pointer to the ADC device object.
+ * @param name Pointer to the device name string.
+ * @param ops Pointer to the ADC driver operation table.
+ * @param user_data Pointer to the driver private data.
+ * @return Operation status.
+ */
+rt_err_t rt_hw_adc_register(rt_adc_device_t device, const char *name, const struct rt_adc_ops *ops, const void *user_data)
+{
+ rt_err_t result;
+
+ if ((device == RT_NULL) || (name == RT_NULL) || (ops == RT_NULL) ||
+ (ops->core == RT_NULL) || (ops->core->session_config == RT_NULL) ||
+ (ops->sequence == RT_NULL) || (ops->sequence->start == RT_NULL) ||
+ (ops->sequence->read == RT_NULL) || (ops->sequence->stop == RT_NULL))
+ {
+ return -RT_EINVAL;
+ }
+
+ device->parent.type = RT_Device_Class_ADC;
+ device->parent.rx_indicate = RT_NULL;
+ device->parent.tx_complete = RT_NULL;
+#ifdef RT_USING_DEVICE_OPS
+ device->parent.ops = &adc_device_ops;
+#else
+ device->parent.init = RT_NULL;
+ device->parent.open = adc_open_device;
+ device->parent.close = adc_close_device;
+ device->parent.read = adc_read_device;
+ device->parent.write = RT_NULL;
+ device->parent.control = adc_control_device;
+#endif /* RT_USING_DEVICE_OPS */
+ device->ops = ops;
+ rt_spin_lock_init(&device->spinlock);
+ rt_atomic_store(&device->state, (rt_atomic_t)RT_ADC_STATE_IDLE);
+ device->default_vref_mv = 0;
+ adc_clear_session_ctrl(device);
+#if defined(RT_ADC_USING_TRIGGER)
+ adc_trigger_init(device);
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+#ifdef RT_ADC_USING_STREAM
+ rt_memset(&device->stream_ctrl, 0, sizeof(device->stream_ctrl));
+#endif /* RT_ADC_USING_STREAM */
+ device->parent.user_data = (void *)user_data;
+
+ result = rt_device_register(&device->parent, name, RT_DEVICE_FLAG_RDWR);
+ if (result == RT_EOK)
+ {
+ LOG_I("device registered: %s", name);
+ }
+ else
+ {
+ LOG_E("device register failed: %s result=%d", name, result);
+ }
+
+ return result;
+}
+
+/**
+ * @brief Read one validated ADC sequence while the caller owns the device state.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the validated sequence request object.
+ * @param keep_state_on_success Whether to keep BUSY state after a successful read.
+ * @param read_count Pointer to the stored sample count.
+ * @return Operation status.
+ *
+ * @note A synchronous sequence request reads one scan frame only. Partial
+ * samples are reported through @p read_count when a read error or timeout
+ * happens after at least one sample is stored.
+ */
+static rt_err_t adc_read_sequence_locked(rt_adc_device_t device, rt_uint32_t channels,
+ const struct rt_adc_sequence_cfg *cfg, rt_bool_t keep_state_on_success, rt_size_t *read_count)
+{
+ rt_err_t result;
+ rt_size_t channel_count;
+
+ *read_count = 0U;
+ channel_count = rt_adc_channel_mask_count(channels);
+
+ result = adc_session_config(device, channels);
+ if (result != RT_EOK)
+ {
+ LOG_E("sequence config failed: device=%s result=%d", device->parent.parent.name, result);
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ return result;
+ }
+
+ result = adc_sequence_transfer_locked(device, channels, cfg, channel_count, read_count);
+
+ if ((result != RT_EOK) || (keep_state_on_success != RT_TRUE))
+ {
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ }
+
+ return result;
+}
+
+/**
+ * @brief Read one ADC sequence frame.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the sequence request object.
+ * @param read_count Pointer to the stored sample count.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_read_sequence(rt_adc_device_t device, rt_uint32_t channels,
+ const struct rt_adc_sequence_cfg *cfg, rt_size_t *read_count)
+{
+ rt_size_t channel_count;
+
+ if ((device == RT_NULL) || (cfg == RT_NULL) || (cfg->buffer == RT_NULL) ||
+ (read_count == RT_NULL) || (channels == 0U) ||
+ (device->ops == RT_NULL) || (device->ops->sequence == RT_NULL) || (device->ops->sequence->start == RT_NULL) ||
+ (device->ops->sequence->read == RT_NULL) || (device->ops->sequence->stop == RT_NULL))
+ {
+ return -RT_EINVAL;
+ }
+
+ *read_count = 0U;
+ channel_count = rt_adc_channel_mask_count(channels);
+ if ((channel_count == 0U) || (cfg->buffer_length < channel_count))
+ {
+ return -RT_EINVAL;
+ }
+
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+
+ return adc_read_sequence_locked(device, channels, cfg, RT_FALSE, read_count);
+}
+
+
+#ifdef RT_ADC_USING_STREAM
+/**
+ * @brief Check whether an ADC stream policy is enabled by configuration.
+ * @param policy ADC stream buffering policy.
+ * @return RT_TRUE if the policy is supported, otherwise RT_FALSE.
+ */
+static rt_bool_t adc_stream_policy_is_supported(enum rt_adc_stream_policy policy)
+{
+ switch (policy)
+ {
+#ifdef RT_ADC_STREAM_USING_LATEST
+ case RT_ADC_STREAM_POLICY_LATEST:
+ return RT_TRUE;
+#endif /* RT_ADC_STREAM_USING_LATEST */
+#ifdef RT_ADC_STREAM_USING_FIFO
+ case RT_ADC_STREAM_POLICY_FIFO:
+ return RT_TRUE;
+#endif /* RT_ADC_STREAM_USING_FIFO */
+ default:
+ return RT_FALSE;
+ }
+}
+
+/**
+ * @brief Resolve the effective DMA data event mode for one stream request.
+ * @param policy Active stream buffering policy.
+ * @param request Requested DMA event mode.
+ * @param effective Pointer to the output effective event mode.
+ * @return Operation status.
+ */
+static rt_err_t adc_stream_resolve_dma_event_mode(enum rt_adc_stream_policy policy,
+ enum rt_adc_stream_dma_event_mode request,
+ enum rt_adc_stream_dma_event_mode *effective)
+{
+ if (request == RT_ADC_STREAM_DMA_EVENT_AUTO)
+ {
+#ifdef RT_ADC_STREAM_USING_LATEST
+ if (policy == RT_ADC_STREAM_POLICY_LATEST)
+ {
+ *effective = RT_ADC_STREAM_DMA_EVENT_NONE;
+ return RT_EOK;
+ }
+#endif /* RT_ADC_STREAM_USING_LATEST */
+#ifdef RT_ADC_STREAM_USING_FIFO
+ if (policy == RT_ADC_STREAM_POLICY_FIFO)
+ {
+ *effective = RT_ADC_STREAM_DMA_EVENT_HALF_FULL;
+ return RT_EOK;
+ }
+#endif /* RT_ADC_STREAM_USING_FIFO */
+ return -RT_ENOSYS;
+ }
+
+#ifdef RT_ADC_STREAM_USING_FIFO
+ if ((policy == RT_ADC_STREAM_POLICY_FIFO) && (request == RT_ADC_STREAM_DMA_EVENT_NONE))
+ {
+ return -RT_EINVAL;
+ }
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+ if ((request != RT_ADC_STREAM_DMA_EVENT_NONE) &&
+ (request != RT_ADC_STREAM_DMA_EVENT_FULL_ONLY) &&
+ (request != RT_ADC_STREAM_DMA_EVENT_HALF_FULL))
+ {
+ return -RT_EINVAL;
+ }
+
+ *effective = request;
+ return RT_EOK;
+}
+
+/**
+ * @brief Reset one ADC stream runtime control block.
+ * @param ctrl Pointer to the stream control block.
+ */
+static void adc_stream_ctrl_reset(struct rt_adc_stream_ctrl *ctrl)
+{
+ rt_memset(ctrl, 0, sizeof(*ctrl));
+}
+
+#ifdef RT_ADC_STREAM_USING_FIFO
+/**
+ * @brief Wake FIFO stream readers when the active stream owns a FIFO.
+ * @param ctrl Pointer to the stream control block.
+ */
+static void adc_stream_fifo_wakeup(struct rt_adc_stream_ctrl *ctrl)
+{
+ if ((ctrl->policy == RT_ADC_STREAM_POLICY_FIFO) && (ctrl->fifo_enabled == RT_TRUE))
+ {
+ rt_completion_done(&ctrl->rx_cpt);
+ }
+}
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+/**
+ * @brief Validate a stream request and initialize the stream control block.
+ * @param device Pointer to the ADC device object.
+ * @param cfg Pointer to the stream configuration object.
+ * @param frame_length Samples in one ADC scan frame.
+ * @return Operation status.
+ */
+static rt_err_t adc_stream_ctrl_configure(rt_adc_device_t device, const struct rt_adc_stream_cfg *cfg, rt_size_t frame_length)
+{
+ struct rt_adc_stream_ctrl *ctrl;
+ enum rt_adc_stream_dma_event_mode dma_event_mode;
+ rt_err_t result;
+
+ if ((cfg->dma_buffer == RT_NULL) || (cfg->dma_buffer_length == 0U) || (frame_length == 0U) ||
+ (adc_stream_policy_is_supported(cfg->policy) != RT_TRUE))
+ {
+ LOG_E("stream ctrl config invalid args: device=%s buffer=%p len=%u frame=%u",
+ device->parent.parent.name, cfg->dma_buffer, (unsigned int)cfg->dma_buffer_length, (unsigned int)frame_length);
+ return -RT_EINVAL;
+ }
+
+ result = adc_stream_resolve_dma_event_mode(cfg->policy, cfg->dma_event_mode, &dma_event_mode);
+ if (result != RT_EOK)
+ {
+ LOG_E("stream ctrl config invalid event mode: device=%s policy=%d mode=%d",
+ device->parent.parent.name, (int)cfg->policy, (int)cfg->dma_event_mode);
+ return result;
+ }
+
+ LOG_D("stream ctrl config start: device=%s policy=%s frame=%u dma=%u event_mode=%d",
+ device->parent.parent.name, adc_stream_policy_name(cfg->policy),
+ (unsigned int)frame_length, (unsigned int)cfg->dma_buffer_length, (int)dma_event_mode);
+ ctrl = &device->stream_ctrl;
+ adc_stream_ctrl_reset(ctrl);
+
+#ifdef RT_ADC_STREAM_USING_LATEST
+ if (cfg->policy == RT_ADC_STREAM_POLICY_LATEST)
+ {
+ if (cfg->dma_buffer_length != frame_length)
+ {
+ LOG_E("latest stream invalid length: device=%s dma=%u frame=%u",
+ device->parent.parent.name, (unsigned int)cfg->dma_buffer_length, (unsigned int)frame_length);
+ return -RT_EINVAL;
+ }
+
+ ctrl->active = RT_TRUE;
+ ctrl->policy = cfg->policy;
+ ctrl->dma_event_mode = dma_event_mode;
+ ctrl->dma_buffer = cfg->dma_buffer;
+ ctrl->dma_buffer_length = cfg->dma_buffer_length;
+ ctrl->frame_length = frame_length;
+ LOG_D("latest stream configured: device=%s frame=%u", device->parent.parent.name, (unsigned int)frame_length);
+ return RT_EOK;
+ }
+#endif /* RT_ADC_STREAM_USING_LATEST */
+
+#ifdef RT_ADC_STREAM_USING_FIFO
+ if (cfg->policy == RT_ADC_STREAM_POLICY_FIFO)
+ {
+ if ((cfg->fifo_buffer == RT_NULL) || (cfg->fifo_buffer_length == 0U))
+ {
+ LOG_E("fifo stream invalid fifo buffer: device=%s len=%u",
+ device->parent.parent.name, (unsigned int)cfg->fifo_buffer_length);
+ return -RT_EINVAL;
+ }
+
+ if ((cfg->watermark != 0U) && (cfg->watermark > cfg->fifo_buffer_length))
+ {
+ LOG_E("fifo stream invalid watermark: device=%s watermark=%u fifo=%u",
+ device->parent.parent.name, (unsigned int)cfg->watermark, (unsigned int)cfg->fifo_buffer_length);
+ return -RT_EINVAL;
+ }
+
+ if (cfg->dma_buffer_length < frame_length)
+ {
+ LOG_E("fifo stream invalid dma length: device=%s dma=%u frame=%u",
+ device->parent.parent.name, (unsigned int)cfg->dma_buffer_length, (unsigned int)frame_length);
+ return -RT_EINVAL;
+ }
+
+ /* The default FIFO start path accepts generic DMA staging buffers and
+ * reports runtime overflow through last_error. Use
+ * rt_adc_stream_start_frame_aligned_fifo() when the caller requires a DMA
+ * data-event block layout that never splits ADC scan frames.
+ */
+ ctrl->active = RT_TRUE;
+ ctrl->policy = cfg->policy;
+ ctrl->dma_event_mode = dma_event_mode;
+ ctrl->dma_buffer = cfg->dma_buffer;
+ ctrl->dma_buffer_length = cfg->dma_buffer_length;
+ ctrl->frame_length = frame_length;
+ ctrl->watermark = cfg->watermark;
+ ctrl->callback = cfg->callback;
+ ctrl->user_data = cfg->user_data;
+ ctrl->overflow_count = 0U;
+ rt_atomic_store(&ctrl->last_error, (rt_atomic_t)RT_EOK);
+ rt_ringbuffer_init(&ctrl->fifo, (rt_uint8_t *)cfg->fifo_buffer, (rt_int32_t)(cfg->fifo_buffer_length * sizeof(rt_uint32_t)));
+ rt_completion_init(&ctrl->rx_cpt);
+ ctrl->fifo_enabled = RT_TRUE;
+ LOG_D("fifo stream configured: device=%s frame=%u dma=%u fifo=%u watermark=%u event_mode=%d",
+ device->parent.parent.name, (unsigned int)frame_length, (unsigned int)cfg->dma_buffer_length,
+ (unsigned int)cfg->fifo_buffer_length, (unsigned int)cfg->watermark, (int)dma_event_mode);
+ return RT_EOK;
+ }
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+ LOG_E("stream policy unsupported: device=%s policy=%d", device->parent.parent.name, (int)cfg->policy);
+ return -RT_ENOSYS;
+}
+
+#ifdef RT_ADC_STREAM_USING_LATEST
+/**
+ * @brief Read the latest ADC rank values from the DMA buffer.
+ * @param device Pointer to the ADC device object.
+ * @param buffer Pointer to the destination sample buffer.
+ * @param sample_count Destination buffer length in samples.
+ * @param timeout_ms Unused timeout parameter.
+ * @return Number of samples read, or a negative RT-Thread error code.
+ *
+ * @note Latest mode is a lockless DMA-buffer view. CPU-side atomic operations,
+ * spin locks, mutexes, or read-write locks cannot prevent the DMA engine
+ * from updating memory concurrently, because DMA does not participate in
+ * CPU locking protocols. This mode only guarantees that each buffer index
+ * maps to the corresponding ADC rank latest value; it does not guarantee
+ * that the copied frame is a single hardware scan-cycle snapshot. Use FIFO
+ * mode when ordered and frame-accurate samples are required.
+ */
+static rt_ssize_t rt_adc_stream_read_latest(rt_adc_device_t device, rt_uint32_t *buffer,
+ rt_size_t sample_count, rt_int32_t timeout_ms)
+{
+ struct rt_adc_stream_ctrl *ctrl;
+ rt_err_t result;
+
+ RT_UNUSED(timeout_ms);
+
+ ctrl = &device->stream_ctrl;
+ if (sample_count < ctrl->frame_length)
+ {
+ LOG_E("latest read buffer too small: device=%s samples=%u frame=%u",
+ device->parent.parent.name, (unsigned int)sample_count, (unsigned int)ctrl->frame_length);
+ return -RT_EINVAL;
+ }
+
+ result = (rt_err_t)rt_atomic_load(&ctrl->last_error);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ if ((device->ops != RT_NULL) && (device->ops->stream != RT_NULL) && (device->ops->stream->sync != RT_NULL))
+ {
+ result = device->ops->stream->sync(device, ctrl->dma_buffer, ctrl->frame_length);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ }
+
+ result = (rt_err_t)rt_atomic_load(&ctrl->last_error);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ rt_memcpy(buffer, ctrl->dma_buffer, ctrl->frame_length * sizeof(rt_uint32_t));
+ return (rt_ssize_t)ctrl->frame_length;
+}
+#endif /* RT_ADC_STREAM_USING_LATEST */
+
+#ifdef RT_ADC_STREAM_USING_FIFO
+/**
+ * @brief Read ordered ADC samples from the framework FIFO.
+ * @param device Pointer to the ADC device object.
+ * @param buffer Pointer to the destination sample buffer.
+ * @param sample_count Number of samples to read before returning unless timeout or error occurs.
+ * @param timeout_ms Total read timeout in milliseconds; negative means wait forever.
+ * @return Number of samples read, or a negative RT-Thread error code.
+ * @note In blocking mode, this function waits until the requested sample count is read or timeout occurs.
+ * On timeout, the partial sample count is returned if any samples were read.
+ */
+static rt_ssize_t rt_adc_stream_read_fifo(rt_adc_device_t device, rt_uint32_t *buffer, rt_size_t sample_count, rt_int32_t timeout_ms)
+{
+ struct rt_adc_stream_ctrl *ctrl;
+ rt_size_t read_count;
+ rt_tick_t timeout_tick;
+ rt_tick_t begin_tick;
+
+ ctrl = &device->stream_ctrl;
+ read_count = 0U;
+ timeout_tick = (timeout_ms < 0) ? RT_WAITING_FOREVER : rt_tick_from_millisecond(timeout_ms);
+ begin_tick = rt_tick_get();
+
+ while (read_count < sample_count)
+ {
+ enum rt_adc_state state;
+ rt_base_t level;
+ rt_size_t got;
+ rt_err_t result;
+ rt_tick_t wait_tick;
+
+ state = adc_get_state(device);
+ if (state != RT_ADC_STATE_STREAM)
+ {
+ return (read_count > 0U) ? (rt_ssize_t)read_count : ((state == RT_ADC_STATE_IDLE) ? -RT_EINVAL : -RT_EBUSY);
+ }
+
+ level = rt_spin_lock_irqsave(&device->spinlock);
+ got = rt_ringbuffer_get(&ctrl->fifo, (rt_uint8_t *)&buffer[read_count],
+ (rt_uint32_t)((sample_count - read_count) * sizeof(rt_uint32_t)));
+ rt_spin_unlock_irqrestore(&device->spinlock, level);
+
+ read_count += got / sizeof(rt_uint32_t);
+ if (read_count >= sample_count)
+ {
+ return (rt_ssize_t)read_count;
+ }
+
+ result = (rt_err_t)rt_atomic_load(&ctrl->last_error);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ if (timeout_ms == RT_WAITING_NO)
+ {
+ return (read_count > 0U) ? (rt_ssize_t)read_count : -RT_ETIMEOUT;
+ }
+
+ if (timeout_ms < 0)
+ {
+ wait_tick = RT_WAITING_FOREVER;
+ }
+ else
+ {
+ rt_tick_t delta_tick;
+
+ delta_tick = rt_tick_get_delta(begin_tick);
+ if (delta_tick >= timeout_tick)
+ {
+ return (read_count > 0U) ? (rt_ssize_t)read_count : -RT_ETIMEOUT;
+ }
+
+ wait_tick = timeout_tick - delta_tick;
+ }
+
+ if (rt_completion_wait(&ctrl->rx_cpt, wait_tick) != RT_EOK)
+ {
+ return (read_count > 0U) ? (rt_ssize_t)read_count : -RT_ETIMEOUT;
+ }
+ }
+
+ return (rt_ssize_t)read_count;
+}
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+/**
+ * @brief Start one ADC stream session.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the stream configuration object.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_stream_start(rt_adc_device_t device, rt_uint32_t channels, const struct rt_adc_stream_cfg *cfg)
+{
+ rt_size_t frame_length;
+ rt_err_t result;
+ rt_bool_t start_called;
+
+ if ((device == RT_NULL) || (cfg == RT_NULL) || (channels == 0U))
+ {
+ return -RT_EINVAL;
+ }
+
+ if ((device->ops == RT_NULL) || (device->ops->stream == RT_NULL) ||
+ (device->ops->stream->start == RT_NULL) || (device->ops->stream->stop == RT_NULL))
+ {
+ return -RT_ENOSYS;
+ }
+
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+ start_called = RT_FALSE;
+#if defined(RT_ADC_USING_TRIGGER)
+ rt_bool_t trigger_started = RT_FALSE;
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+ frame_length = rt_adc_channel_mask_count(channels);
+ result = adc_cache_session_ctrl(device, channels);
+ if (result != RT_EOK)
+ {
+ goto fail_after_lock;
+ }
+
+ result = adc_stream_ctrl_configure(device, cfg, frame_length);
+ if (result != RT_EOK)
+ {
+ goto fail_after_lock;
+ }
+
+#if defined(RT_ADC_USING_TRIGGER)
+ result = adc_trigger_preconfig(device);
+ if (result != RT_EOK)
+ {
+ goto fail_after_lock;
+ }
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+ start_called = RT_TRUE;
+ result = device->ops->stream->start(device, channels, cfg);
+ if (result != RT_EOK)
+ {
+ goto fail_after_lock;
+ }
+
+ result = (rt_err_t)rt_atomic_load(&device->stream_ctrl.last_error);
+ if (result != RT_EOK)
+ {
+ goto fail_after_lock;
+ }
+
+#if defined(RT_ADC_USING_TRIGGER)
+ result = adc_trigger_source_control(device, ADC_TRIGGER_SOURCE_START, &trigger_started, RT_EOK);
+ if (result != RT_EOK)
+ {
+ goto fail_after_lock;
+ }
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+ adc_set_state(device, RT_ADC_STATE_STREAM);
+ return RT_EOK;
+
+fail_after_lock:
+ if(result != RT_EOK)
+ {
+ LOG_E("stream start failed: device=%s result=%d", device->parent.parent.name, result);
+ }
+#if defined(RT_ADC_USING_TRIGGER)
+ result = adc_trigger_source_control(device, ADC_TRIGGER_SOURCE_STOP, &trigger_started, result);
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+ if (start_called == RT_TRUE)
+ {
+ rt_err_t stop_result;
+ rt_bool_t hardware_stopped;
+
+ hardware_stopped = RT_FALSE;
+ stop_result = device->ops->stream->stop(device, &hardware_stopped);
+ if ((stop_result != RT_EOK) && (hardware_stopped != RT_TRUE))
+ {
+ adc_set_state(device, RT_ADC_STATE_STREAM);
+ return stop_result;
+ }
+ }
+
+ adc_stream_ctrl_reset(&device->stream_ctrl);
+ adc_clear_session_ctrl(device);
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ return result;
+}
+
+#ifdef RT_ADC_STREAM_USING_FIFO
+/**
+ * @brief Start a FIFO ADC stream with strict frame-aligned buffer layout.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the stream configuration object.
+ * @return Operation status.
+ *
+ * @note This helper is intended for continuous FIFO sampling where DMA
+ * data-event blocks must not split ADC scan frames. It validates that
+ * each DMA event block contains complete frames and that the FIFO can
+ * accept at least one complete event block.
+ * @note Passing this validation does not prove that samples can never be lost.
+ * The reader must still drain the FIFO fast enough for the selected
+ * sample rate and scheduler latency. Runtime overflow is still reported
+ * through the stream error state.
+ */
+rt_err_t rt_adc_stream_start_frame_aligned_fifo(rt_adc_device_t device, rt_uint32_t channels, const struct rt_adc_stream_cfg *cfg)
+{
+ rt_size_t frame_length;
+ rt_size_t block_length;
+ enum rt_adc_stream_dma_event_mode dma_event_mode;
+ rt_err_t result;
+
+ if ((device == RT_NULL) || (cfg == RT_NULL) || (channels == 0U) ||
+ (cfg->policy != RT_ADC_STREAM_POLICY_FIFO) ||
+ (cfg->dma_buffer == RT_NULL) || (cfg->dma_buffer_length == 0U) ||
+ (cfg->fifo_buffer == RT_NULL) || (cfg->fifo_buffer_length == 0U))
+ {
+ return -RT_EINVAL;
+ }
+
+ frame_length = rt_adc_channel_mask_count(channels);
+ if (frame_length == 0U)
+ {
+ return -RT_EINVAL;
+ }
+
+ result = adc_stream_resolve_dma_event_mode(cfg->policy, cfg->dma_event_mode, &dma_event_mode);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ block_length = (dma_event_mode == RT_ADC_STREAM_DMA_EVENT_FULL_ONLY) ? cfg->dma_buffer_length : (cfg->dma_buffer_length / 2U);
+ if ((cfg->dma_buffer_length < frame_length) || /* DMA buffer must contain at least one complete scan frame. */
+ ((cfg->dma_buffer_length % frame_length) != 0U) || /* DMA buffer length must be aligned to complete scan frames. */
+ (block_length == 0U) || /* The selected DMA event mode must produce a non-empty staging block. */
+ ((block_length % frame_length) != 0U) || /* Each staging block copied to FIFO must contain complete scan frames only. */
+ (cfg->fifo_buffer_length < block_length) || /* FIFO must be able to store at least one completed DMA staging block. */
+ ((cfg->fifo_buffer_length % frame_length) != 0U)) /* FIFO capacity must also be aligned to complete scan frames. */
+ {
+ LOG_E("frame-aligned fifo stream invalid layout: device=%s dma=%u block=%u frame=%u fifo=%u",
+ device->parent.parent.name, (unsigned int)cfg->dma_buffer_length,
+ (unsigned int)block_length, (unsigned int)frame_length,
+ (unsigned int)cfg->fifo_buffer_length);
+ return -RT_EINVAL;
+ }
+
+ if ((dma_event_mode == RT_ADC_STREAM_DMA_EVENT_HALF_FULL) && ((cfg->dma_buffer_length % 2U) != 0U))
+ {
+ return -RT_EINVAL;
+ }
+
+ return rt_adc_stream_start(device, channels, cfg);
+}
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+/**
+ * @brief Read converted samples from an active ADC stream session.
+ * @param device Pointer to the ADC device object.
+ * @param buffer Pointer to the destination sample buffer.
+ * @param sample_count Number of samples to read.
+ * @param timeout_ms Read timeout in milliseconds; negative means wait forever.
+ * @return Number of samples read, or a negative RT-Thread error code.
+ */
+rt_ssize_t rt_adc_stream_read(rt_adc_device_t device, rt_uint32_t *buffer, rt_size_t sample_count, rt_int32_t timeout_ms)
+{
+ struct rt_adc_stream_ctrl *ctrl;
+ rt_ssize_t read_count;
+
+ if ((device == RT_NULL) || (buffer == RT_NULL) || (sample_count == 0U))
+ {
+ return -RT_EINVAL;
+ }
+
+ if (adc_get_state(device) != RT_ADC_STATE_STREAM)
+ {
+ return -RT_EINVAL;
+ }
+
+ ctrl = &device->stream_ctrl;
+ switch (ctrl->policy)
+ {
+#ifdef RT_ADC_STREAM_USING_LATEST
+ case RT_ADC_STREAM_POLICY_LATEST:
+ read_count = rt_adc_stream_read_latest(device, buffer, sample_count, timeout_ms);
+ break;
+#endif /* RT_ADC_STREAM_USING_LATEST */
+#ifdef RT_ADC_STREAM_USING_FIFO
+ case RT_ADC_STREAM_POLICY_FIFO:
+ read_count = rt_adc_stream_read_fifo(device, buffer, sample_count, timeout_ms);
+ break;
+#endif /* RT_ADC_STREAM_USING_FIFO */
+ default:
+ read_count = -RT_EINVAL;
+ break;
+ }
+
+ return read_count;
+}
+
+/**
+ * @brief Cancel a blocked ADC stream reader before stream shutdown.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_stream_cancel(rt_adc_device_t device)
+{
+ struct rt_adc_stream_ctrl *ctrl;
+ rt_atomic_t old_error;
+ enum rt_adc_state state;
+
+ if (device == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ state = adc_get_state(device);
+ if (state != RT_ADC_STATE_STREAM)
+ {
+ return (state == RT_ADC_STATE_IDLE) ? -RT_EINVAL : -RT_EBUSY;
+ }
+
+ ctrl = &device->stream_ctrl;
+ old_error = (rt_atomic_t)RT_EOK;
+ (void)rt_atomic_compare_exchange_strong(&ctrl->last_error, &old_error, (rt_atomic_t)-RT_EINTR);
+
+#ifdef RT_ADC_STREAM_USING_FIFO
+ adc_stream_fifo_wakeup(ctrl);
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+ return RT_EOK;
+}
+
+/**
+ * @brief Stop one ADC stream session.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_stream_stop(rt_adc_device_t device)
+{
+ rt_bool_t hardware_stopped;
+ rt_err_t result;
+ rt_err_t trigger_result;
+ enum rt_adc_state state;
+
+ if (device == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ if ((device->ops == RT_NULL) || (device->ops->stream == RT_NULL) || (device->ops->stream->stop == RT_NULL))
+ {
+ return -RT_ENOSYS;
+ }
+
+ state = adc_get_state(device);
+ if (state == RT_ADC_STATE_IDLE)
+ {
+ return RT_EOK;
+ }
+ if (state != RT_ADC_STATE_STREAM)
+ {
+ return -RT_EBUSY;
+ }
+
+ if (adc_try_set_state(device, RT_ADC_STATE_STREAM, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+
+#if defined(RT_ADC_USING_TRIGGER)
+ rt_bool_t trigger_started = RT_TRUE;
+ trigger_result = adc_trigger_source_control(device, ADC_TRIGGER_SOURCE_STOP, &trigger_started, RT_EOK);
+#else
+ trigger_result = RT_EOK;
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+ hardware_stopped = RT_FALSE;
+ result = device->ops->stream->stop(device, &hardware_stopped);
+ if ((result == RT_EOK) && (trigger_result != RT_EOK))
+ {
+ result = trigger_result;
+ }
+
+ if ((result == RT_EOK) || (hardware_stopped == RT_TRUE))
+ {
+#ifdef RT_ADC_STREAM_USING_FIFO
+ rt_atomic_store(&device->stream_ctrl.last_error, (rt_atomic_t)((result == RT_EOK) ? -RT_EINVAL : result));
+ adc_stream_fifo_wakeup(&device->stream_ctrl);
+ device->stream_ctrl.fifo_enabled = RT_FALSE;
+#endif /* RT_ADC_STREAM_USING_FIFO */
+ adc_stream_ctrl_reset(&device->stream_ctrl);
+ adc_clear_session_ctrl(device);
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ }
+ else
+ {
+ adc_set_state(device, RT_ADC_STATE_STREAM);
+ }
+
+ return result;
+}
+
+/**
+ * @brief ADC stream ISR event handler used by low-level drivers.
+ * @param device Pointer to the ADC device object.
+ * @param event Stream event.
+ * @param sample_buffer Pointer to the completed DMA sample block.
+ * @param sample_count Completed sample count.
+ * @return Operation status.
+ */
+rt_err_t rt_hw_adc_stream_isr(rt_adc_device_t device, enum rt_adc_stream_event event,
+ const rt_uint32_t *sample_buffer, rt_size_t sample_count)
+{
+ struct rt_adc_stream_ctrl *ctrl;
+ enum rt_adc_state state;
+#ifdef RT_ADC_STREAM_USING_FIFO
+ const rt_uint8_t *src;
+ rt_size_t bytes;
+ rt_size_t put_len;
+ rt_size_t data_len;
+ rt_base_t level;
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+ if (device == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ ctrl = &device->stream_ctrl;
+ if (event == RT_ADC_STREAM_EVENT_ERROR)
+ {
+ rt_atomic_t old_error;
+
+ old_error = (rt_atomic_t)RT_EOK;
+ if (rt_atomic_compare_exchange_strong(&ctrl->last_error, &old_error, (rt_atomic_t)-RT_ERROR) == RT_TRUE)
+ {
+ LOG_W("stream error: device=%s event=%d", device->parent.parent.name, event);
+ }
+#ifdef RT_ADC_STREAM_USING_FIFO
+ adc_stream_fifo_wakeup(ctrl);
+#endif /* RT_ADC_STREAM_USING_FIFO */
+ return RT_EOK;
+ }
+
+ state = adc_get_state(device);
+#ifdef RT_ADC_STREAM_USING_FIFO
+ if ((state != RT_ADC_STATE_STREAM) && !((state == RT_ADC_STATE_LOCKED) && (ctrl->fifo_enabled == RT_TRUE)))
+ {
+ return RT_EOK;
+ }
+#else
+ if (state != RT_ADC_STATE_STREAM)
+ {
+ return RT_EOK;
+ }
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+ if ((event != RT_ADC_STREAM_EVENT_DMA_HALF) && (event != RT_ADC_STREAM_EVENT_DMA_DONE))
+ {
+ return -RT_EINVAL;
+ }
+
+ if ((rt_err_t)rt_atomic_load(&ctrl->last_error) != RT_EOK)
+ {
+ return RT_EOK;
+ }
+
+#ifdef RT_ADC_STREAM_USING_FIFO
+ if ((ctrl->policy != RT_ADC_STREAM_POLICY_FIFO) || (ctrl->fifo_enabled != RT_TRUE))
+ {
+ return RT_EOK;
+ }
+
+ if ((sample_buffer == RT_NULL) || (sample_count == 0U))
+ {
+ return -RT_EINVAL;
+ }
+
+ src = (const rt_uint8_t *)sample_buffer;
+ bytes = sample_count * sizeof(rt_uint32_t);
+
+ level = rt_spin_lock_irqsave(&device->spinlock);
+ put_len = rt_ringbuffer_put(&ctrl->fifo, src, (rt_uint32_t)bytes);
+ data_len = rt_ringbuffer_data_len(&ctrl->fifo);
+ rt_spin_unlock_irqrestore(&device->spinlock, level);
+
+ if (put_len < bytes)
+ {
+ rt_atomic_t old_error;
+ ctrl->overflow_count++;
+ old_error = (rt_atomic_t)RT_EOK;
+ if (rt_atomic_compare_exchange_strong(&ctrl->last_error, &old_error, (rt_atomic_t)-RT_EFULL) == RT_TRUE)
+ {
+ LOG_W("stream fifo overflow: device=%s put=%u expected=%u data_len=%u",
+ device->parent.parent.name, (unsigned int)put_len, (unsigned int)bytes, (unsigned int)data_len);
+ }
+ adc_stream_fifo_wakeup(ctrl);
+ }
+
+ if ((put_len > 0U) && ((ctrl->watermark == 0U) || (data_len >= (ctrl->watermark * sizeof(rt_uint32_t)))))
+ {
+ adc_stream_fifo_wakeup(ctrl);
+
+ if (ctrl->callback != RT_NULL)
+ {
+ ctrl->callback(device, put_len / sizeof(rt_uint32_t), ctrl->user_data);
+ }
+ }
+
+ return RT_EOK;
+#else
+ RT_UNUSED(sample_buffer);
+ RT_UNUSED(sample_count);
+ return RT_EOK;
+#endif /* RT_ADC_STREAM_USING_FIFO */
+}
+#endif /* RT_ADC_USING_STREAM */
+
+/**
+ * @brief Convert one ADC raw sample to millivolts.
+ * @param raw_value Raw ADC sample code.
+ * @param vref_mv ADC reference voltage in millivolts.
+ * @param resolution_bits ADC resolution in bits.
+ * @param voltage_mv Pointer to the output voltage in millivolts.
+ * @return Operation status.
+ *
+ * ADC codes are treated as an inclusive range from 0 to
+ * ``(1 << resolution_bits) - 1``. The calculation uses a 64-bit
+ * intermediate value and rounds to the nearest integer millivolt.
+ */
+rt_err_t rt_adc_raw_to_voltage_mv(rt_uint32_t raw_value, rt_uint32_t vref_mv,
+ rt_uint8_t resolution_bits, rt_uint32_t *voltage_mv)
+{
+ rt_uint64_t full_scale_code;
+
+ if ((voltage_mv == RT_NULL) || (vref_mv == 0U) || (resolution_bits == 0U) || (resolution_bits > 32U))
+ {
+ return -RT_EINVAL;
+ }
+
+ full_scale_code = ((rt_uint64_t)1 << resolution_bits) - 1ULL;
+ if ((rt_uint64_t)raw_value > full_scale_code)
+ {
+ return -RT_EINVAL;
+ }
+
+ /*
+ * Convert ADC code to millivolts:
+ *
+ * voltage_mv = raw * vref_mv / full_scale_code
+ * full_scale_code = (1 << resolution_bits) - 1
+ *
+ * ADC codes are treated as an inclusive range from 0 to full scale.
+ * The multiplication is promoted to 64-bit first, then half of the
+ * denominator is added to round to the nearest integer millivolt instead
+ * of truncating non-exact divisions toward zero.
+ */
+ *voltage_mv = (rt_uint32_t)(((rt_uint64_t)raw_value * (rt_uint64_t)vref_mv + (full_scale_code / 2ULL)) / full_scale_code);
+
+ return RT_EOK;
+}
+
+/**
+ * @brief Convert one ADC raw sample to a scaled voltage in millivolts.
+ * @param raw_value Raw ADC sample code.
+ * @param vref_mv ADC reference voltage in millivolts.
+ * @param resolution_bits ADC resolution in bits.
+ * @param scale_num Voltage scaling numerator.
+ * @param scale_den Voltage scaling denominator.
+ * @param voltage_mv Pointer to the output scaled voltage in millivolts.
+ * @return Operation status.
+ *
+ * The scaled voltage is calculated as:
+ *
+ * raw_to_voltage_mv(raw_value) * scale_num / scale_den
+ *
+ * @note @p scale_num and @p scale_den may describe any positive rational
+ * scaling ratio that does not overflow the internal calculation. The
+ * final scaled millivolt value must fit in rt_uint32_t.
+ *
+ * @retval RT_EOK The conversion completed successfully.
+ * @retval -RT_EINVAL A pointer is null, a scale argument is zero, the ADC
+ * conversion arguments are invalid, or the scaled result overflows
+ * rt_uint32_t.
+ */
+rt_err_t rt_adc_raw_to_scaled_voltage_mv(rt_uint32_t raw_value, rt_uint32_t vref_mv,
+ rt_uint8_t resolution_bits, rt_uint32_t scale_num,
+ rt_uint32_t scale_den, rt_uint32_t *voltage_mv)
+{
+ rt_uint32_t input_mv;
+ rt_uint64_t numerator;
+ rt_uint64_t rounding;
+ rt_uint64_t scaled_mv;
+ rt_err_t result;
+
+ if ((voltage_mv == RT_NULL) || (scale_num == 0U) || (scale_den == 0U))
+ {
+ return -RT_EINVAL;
+ }
+
+ result = rt_adc_raw_to_voltage_mv(raw_value, vref_mv, resolution_bits, &input_mv);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ numerator = (rt_uint64_t)input_mv * (rt_uint64_t)scale_num;
+ rounding = (rt_uint64_t)scale_den / 2ULL;
+
+ if (numerator > (((rt_uint64_t)~0ULL) - rounding))
+ {
+ return -RT_EINVAL;
+ }
+
+ scaled_mv = (numerator + rounding) / (rt_uint64_t)scale_den;
+ if (scaled_mv > RT_UINT32_MAX)
+ {
+ return -RT_EINVAL;
+ }
+
+ *voltage_mv = (rt_uint32_t)scaled_mv;
+ return RT_EOK;
+}
+
+/**
+ * @brief Read voltage values from the configured ADC session.
+ * @param device Pointer to the ADC device object.
+ * @param voltages_mv Pointer to the output voltage buffer in millivolts.
+ * @param size Output voltage buffer length in samples.
+ * @param timeout_ms Read timeout in milliseconds; negative means wait forever.
+ * @return Operation status.
+ *
+ * @note @p size must match the configured session channel count. The output
+ * order follows the ascending bit order of the configured channel mask.
+ * The configured default_vref_mv is preferred for voltage conversion. If
+ * default_vref_mv is not configured, the backend VREF channel is used for
+ * VDDA calculation only when it is present in the configured session.
+ */
+rt_err_t rt_adc_voltage(rt_adc_device_t device, rt_uint32_t *voltages_mv, rt_size_t size, rt_int32_t timeout_ms)
+{
+ rt_uint32_t vref_mv;
+ rt_uint32_t vref_value;
+ rt_uint8_t resolution_bits;
+ rt_uint8_t vref_channel;
+ rt_size_t vref_index;
+ rt_size_t index;
+ struct rt_adc_sequence_cfg sequence_cfg;
+ rt_size_t read_count;
+ rt_err_t result;
+
+ if ((device == RT_NULL) || (voltages_mv == RT_NULL))
+ {
+ return -RT_EINVAL;
+ }
+
+ if ((device->ops == RT_NULL) || (device->ops->core == RT_NULL) || (device->ops->core->control == RT_NULL))
+ {
+ return -RT_ENOSYS;
+ }
+
+ if (adc_try_set_state(device, RT_ADC_STATE_IDLE, RT_ADC_STATE_LOCKED) != RT_TRUE)
+ {
+ return -RT_EBUSY;
+ }
+
+ LOG_D("voltage request: device=%s count=%u timeout=%ld", device->parent.parent.name, (unsigned int)size, (long)timeout_ms);
+
+ if ((device->session_ctrl.configured != RT_TRUE) || (device->session_ctrl.channel_count == 0U) || (size != device->session_ctrl.channel_count))
+ {
+ result = -RT_EINVAL;
+ goto out;
+ }
+
+ rt_memset(&sequence_cfg, 0, sizeof(sequence_cfg));
+ sequence_cfg.buffer = voltages_mv;
+ sequence_cfg.buffer_length = size;
+ sequence_cfg.timeout_ms = timeout_ms;
+ result = adc_sequence_transfer_locked(device, device->session_ctrl.channels,
+ &sequence_cfg, size, &read_count);
+ if ((result == RT_EOK) && (read_count != size))
+ {
+ result = -RT_ERROR;
+ }
+ if (result != RT_EOK)
+ {
+ LOG_E("voltage sample read failed: device=%s result=%d", device->parent.parent.name, result);
+ goto out;
+ }
+
+ result = device->ops->core->control(device, RT_ADC_CMD_GET_RESOLUTION, &resolution_bits);
+ if (result != RT_EOK)
+ {
+ goto out;
+ }
+
+ vref_mv = 0U;
+ if (device->default_vref_mv != 0U)
+ {
+ vref_mv = device->default_vref_mv;
+ result = RT_EOK;
+ }
+ else
+ {
+ vref_channel = 0U;
+ result = device->ops->core->control(device, RT_ADC_CMD_GET_VREF_CHANNEL, &vref_channel);
+ if ((result == RT_EOK) && (vref_channel < 32U) && ((device->session_ctrl.channels & RT_ADC_CHANNEL_MASK(vref_channel)) != 0U))
+ {
+ result = rt_adc_session_channel_index(device->session_ctrl.channels, RT_ADC_CHANNEL_MASK(vref_channel), &vref_index);
+ if (result != RT_EOK)
+ {
+ goto out;
+ }
+
+ vref_value = voltages_mv[vref_index];
+ result = device->ops->core->control(device, RT_ADC_CMD_CALC_VREF, &vref_value);
+ if (result != RT_EOK)
+ {
+ LOG_E("vref calculate failed: device=%s raw=%lu result=%d",
+ device->parent.parent.name, (unsigned long)voltages_mv[vref_index], result);
+ goto out;
+ }
+
+ if (vref_value == 0U)
+ {
+ result = -RT_EINVAL;
+ goto out;
+ }
+
+ vref_mv = vref_value;
+ }
+ else
+ {
+ result = -RT_EINVAL;
+ goto out;
+ }
+ }
+
+ for (index = 0U; index < size; index++)
+ {
+ result = rt_adc_raw_to_voltage_mv(voltages_mv[index], vref_mv, resolution_bits, &voltages_mv[index]);
+ if (result != RT_EOK)
+ {
+ goto out;
+ }
+ }
+
+ LOG_D("voltage done: device=%s count=%u vref=%lu resolution=%u",
+ device->parent.parent.name, (unsigned int)size,
+ (unsigned long)vref_mv, (unsigned int)resolution_bits);
+ result = RT_EOK;
+
+out:
+ adc_set_state(device, RT_ADC_STATE_IDLE);
+ return result;
+}
+
+#endif /* defined(RT_USING_ADC_V2) */
diff --git a/components/drivers/adc/adc_v2_internal.h b/components/drivers/adc/adc_v2_internal.h
new file mode 100644
index 0000000000..1f97f17dd8
--- /dev/null
+++ b/components/drivers/adc/adc_v2_internal.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2006-2026, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @file adc_v2_internal.h
+ * @brief Internal ADC V2 framework declarations.
+ */
+
+#ifndef __ADC_V2_INTERNAL_H__
+#define __ADC_V2_INTERNAL_H__
+
+#include <rtconfig.h>
+#include <drivers/adc_v2.h>
+
+#if defined(RT_ADC_USING_TRIGGER)
+/**
+ * @brief Initialize the cached ADC trigger state to the default software-started mode.
+ * @param device Pointer to the ADC device object.
+ */
+void adc_trigger_init(rt_adc_device_t device);
+
+/**
+ * @brief Preconfigure the active device-level ADC trigger before hardware configuration.
+ * @param device Pointer to the ADC device object.
+ * @return RT_EOK on success, otherwise an RT-Thread error code.
+ */
+rt_err_t adc_trigger_preconfig(rt_adc_device_t device);
+
+/**
+ * @brief Get the active device-level ADC trigger configuration.
+ * @param device Pointer to the ADC device object.
+ * @return Pointer to the cached trigger configuration, or RT_NULL when no hardware trigger is configured.
+ */
+const struct rt_adc_trigger_cfg *adc_trigger_active_get(rt_adc_device_t device);
+
+/**
+ * @brief Trigger-source lifecycle actions used after ADC hardware is armed.
+ */
+enum adc_trigger_source_action
+{
+ ADC_TRIGGER_SOURCE_START = 0, /**< Start the active trigger source. */
+ ADC_TRIGGER_SOURCE_STOP /**< Stop the previously started trigger source. */
+};
+
+/**
+ * @brief Control the active trigger source around an armed ADC conversion path.
+ * @param device Pointer to the ADC device object.
+ * @param action Trigger-source lifecycle action.
+ * @param started Pointer to the trigger-start state.
+ * @param current_result Transfer result before trigger stop, ignored for start.
+ * @return For start, trigger-source start result; for stop, @p current_result unless trigger stop is the first
+ * observed failure.
+ */
+rt_err_t adc_trigger_source_control(rt_adc_device_t device, enum adc_trigger_source_action action,
+ rt_bool_t *started, rt_err_t current_result);
+
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+#endif /* __ADC_V2_INTERNAL_H__ */
diff --git a/components/drivers/adc/adc_v2_msh.c b/components/drivers/adc/adc_v2_msh.c
new file mode 100644
index 0000000000..7bc42f6cbf
--- /dev/null
+++ b/components/drivers/adc/adc_v2_msh.c
@@ -0,0 +1,1225 @@
+/*
+ * Copyright (c) 2006-2026, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-05-09 wdfk-prog split adc v2 msh command implementation
+ */
+
+/**
+ * @file adc_v2_msh.c
+ * @brief ADC V2 FinSH/MSH command implementation.
+ */
+
+#include <rtconfig.h>
+
+#if defined(RT_USING_ADC_V2) && defined(RT_USING_FINSH) && defined(RT_ADC_V2_USING_MSH)
+
+#include <stdlib.h>
+
+#include <rtdevice.h>
+#include <rtthread.h>
+#include <finsh.h>
+
+#include <drivers/adc_v2.h>
+
+/** @brief Default FinSH temporary read buffer capacity in samples. */
+#define RT_ADC_MSH_VALUE_BUFFER_SIZE 32U
+
+/** @brief Default timeout in milliseconds for ADC MSH sequence read command. */
+#define RT_ADC_MSH_SEQ_DEFAULT_TIMEOUT_MS 1000
+
+/** @brief Default FinSH stream read timeout in milliseconds. */
+#define RT_ADC_MSH_STREAM_READ_TIMEOUT_MS 1000
+
+/** @brief Default FinSH stream DMA buffer capacity in samples. */
+#define RT_ADC_MSH_STREAM_DMA_BUFFER_SIZE 32U
+
+/** @brief Default FIFO storage capacity in samples for the ADC MSH FIFO stream test. */
+#define RT_ADC_MSH_FIFO_TEST_FIFO_BUFFER_SIZE 512U
+
+/**
+ * @brief Print backend-specific ADC FinSH command usage.
+ */
+rt_weak void rt_adc_msh_special_help(void)
+{
+}
+
+/**
+ * @brief Handle backend-specific ADC FinSH commands.
+ * @param device Pointer to the selected ADC device object.
+ * @param argc Argument count excluding the top-level adc command.
+ * @param argv Argument vector excluding the top-level adc command.
+ * @return Operation status.
+ */
+rt_weak rt_err_t rt_adc_msh_special(rt_adc_device_t device, int argc, char **argv)
+{
+ RT_UNUSED(device);
+ RT_UNUSED(argc);
+ RT_UNUSED(argv);
+
+ return -RT_ENOSYS;
+}
+
+#if defined(RT_ADC_USING_TRIGGER)
+/**
+ * @brief Print ADC trigger MSH command usage.
+ */
+static void adc_msh_usage_trigger(void)
+{
+#if defined(RT_ADC_TRIGGER_USING_TIMER)
+ rt_kprintf(" adc trigger set timer_update <timer_device> <freq_hz>\n");
+ rt_kprintf(" adc trigger set timer_compare <timer_device> <timer_ch> <freq_hz>\n");
+#endif /* defined(RT_ADC_TRIGGER_USING_TIMER) */
+#if defined(RT_ADC_TRIGGER_USING_COMPARE)
+ rt_kprintf(" adc trigger set compare [comparator_device] <num> <rising|falling|both>, num: 0~31\n");
+#endif /* defined(RT_ADC_TRIGGER_USING_COMPARE) */
+ rt_kprintf(" adc trigger clear\n");
+ rt_kprintf(" adc trigger status\n");
+}
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+#if defined(RT_ADC_USING_STREAM)
+/**
+ * @brief Print ADC stream MSH command usage.
+ */
+static void adc_msh_usage_stream(void)
+{
+ rt_kprintf(" adc stream start <num0> [num1 ...], num: 0~31\n");
+ rt_kprintf(" adc stream read [timeout_ms]\n");
+ rt_kprintf(" adc stream cancel\n");
+ rt_kprintf(" adc stream stop\n");
+#if defined(RT_ADC_MSH_USING_FIFO_TEST) && defined(RT_ADC_STREAM_USING_FIFO)
+ rt_kprintf(" adc fifo <timeout_ms> <num0> [num1 ...]\n");
+#endif /* defined(RT_ADC_MSH_USING_FIFO_TEST) && defined(RT_ADC_STREAM_USING_FIFO) */
+}
+#endif /* defined(RT_ADC_USING_STREAM) */
+
+/**
+ * @brief Print ADC FinSH command usage.
+ */
+static void adc_msh_usage(void)
+{
+ rt_kprintf("Usage:\n");
+ rt_kprintf(" adc probe <device>\n");
+ rt_kprintf(" adc close\n");
+ rt_kprintf(" adc config <num0> [num1 ...], num: 0~31, eg: 0 1 10\n");
+ rt_kprintf(" adc read\n");
+ rt_kprintf(" adc voltage <timeout_ms>\n");
+ rt_kprintf(" voltage reads the current session configured by adc config\n");
+ rt_kprintf(" if default_vref is absent, adc config must include the backend VREF channel\n");
+ rt_kprintf(" adc seq <timeout_ms> <num0> [num1 ...], num: 0~31, eg: 0 1 10\n");
+#if defined(RT_ADC_USING_TRIGGER)
+ adc_msh_usage_trigger();
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+#if defined(RT_ADC_USING_STREAM)
+ adc_msh_usage_stream();
+#endif /* defined(RT_ADC_USING_STREAM) */
+
+ rt_adc_msh_special_help();
+}
+
+/**
+ * @brief Convert one shell argument to a single-channel mask.
+ * @param arg Pointer to the shell argument string.
+ * @param channels Pointer to the output channel mask.
+ * @return Operation status.
+ */
+static rt_err_t adc_msh_parse_channel_mask(const char *arg, rt_uint32_t *channels)
+{
+ char *endptr;
+ unsigned long value;
+
+ if ((*arg == '\0') || (*arg == '-'))
+ {
+ return -RT_EINVAL;
+ }
+
+ endptr = RT_NULL;
+ value = strtoul(arg, &endptr, 0);
+ if ((endptr == arg) || (endptr == RT_NULL) || (*endptr != '\0') || (value >= 32UL))
+ {
+ return -RT_EINVAL;
+ }
+
+ *channels = RT_ADC_CHANNEL_MASK(value);
+ return RT_EOK;
+}
+
+/**
+ * @brief Convert one shell argument to a timeout value.
+ * @param arg Pointer to the shell argument string.
+ * @return Timeout in milliseconds; negative means wait forever.
+ *
+ * @note This command keeps the permissive MSH parsing convention: strtol()
+ * is used without endptr validation, so a non-numeric string with no
+ * converted digits is treated as 0 by the C library conversion path. A
+ * timeout value of 0 means non-blocking/immediate timeout.
+ */
+static rt_int32_t adc_msh_parse_timeout(const char *arg)
+{
+ return (rt_int32_t)strtol(arg, RT_NULL, 0);
+}
+
+#if defined(RT_ADC_USING_TRIGGER) && (defined(RT_ADC_TRIGGER_USING_TIMER) || defined(RT_ADC_TRIGGER_USING_COMPARE))
+/**
+ * @brief Convert one shell argument to an unsigned integer.
+ * @param arg Pointer to the shell argument string.
+ * @param max_value Maximum accepted value.
+ * @param value Pointer to the output value.
+ * @return Operation status.
+ */
+static rt_err_t adc_msh_parse_ulong(const char *arg, unsigned long max_value, unsigned long *value)
+{
+ char *endptr;
+ unsigned long parsed;
+
+ if ((arg == RT_NULL) || (value == RT_NULL) || (*arg == '\0') || (*arg == '-'))
+ {
+ return -RT_EINVAL;
+ }
+
+ endptr = RT_NULL;
+ parsed = strtoul(arg, &endptr, 0);
+ if ((endptr == arg) || (endptr == RT_NULL) || (*endptr != '\0') || (parsed > max_value))
+ {
+ return -RT_EINVAL;
+ }
+
+ *value = parsed;
+ return RT_EOK;
+}
+
+#if defined(RT_ADC_TRIGGER_USING_COMPARE)
+/**
+ * @brief Convert one shell argument to an ADC trigger edge selector.
+ * @param arg Pointer to the shell argument string.
+ * @param edge Pointer to the output trigger edge selector.
+ * @return Operation status.
+ */
+static rt_err_t adc_msh_parse_trigger_edge(const char *arg, enum rt_adc_trigger_edge *edge)
+{
+ if ((arg == RT_NULL) || (edge == RT_NULL))
+ {
+ return -RT_EINVAL;
+ }
+
+ if (!rt_strcmp(arg, "rising"))
+ {
+ *edge = RT_ADC_TRIGGER_EDGE_RISING;
+ return RT_EOK;
+ }
+
+ if (!rt_strcmp(arg, "falling"))
+ {
+ *edge = RT_ADC_TRIGGER_EDGE_FALLING;
+ return RT_EOK;
+ }
+
+ if (!rt_strcmp(arg, "both"))
+ {
+ *edge = RT_ADC_TRIGGER_EDGE_BOTH;
+ return RT_EOK;
+ }
+
+ return -RT_EINVAL;
+}
+
+#endif /* defined(RT_ADC_TRIGGER_USING_COMPARE) */
+
+/**
+ * @brief Find one trigger source device by shell argument.
+ * @param name Pointer to the source device name string.
+ * @param source Pointer to the output source device object.
+ * @return Operation status.
+ */
+static rt_err_t adc_msh_find_trigger_source(const char *name, rt_device_t *source)
+{
+ rt_device_t dev;
+
+ if ((name == RT_NULL) || (source == RT_NULL))
+ {
+ return -RT_EINVAL;
+ }
+
+ dev = rt_device_find(name);
+ if (dev == RT_NULL)
+ {
+ rt_kprintf("trigger source %s not found\n", name);
+ return -RT_ERROR;
+ }
+
+ *source = dev;
+ return RT_EOK;
+}
+#endif /* defined(RT_ADC_USING_TRIGGER) && (defined(RT_ADC_TRIGGER_USING_TIMER) || defined(RT_ADC_TRIGGER_USING_COMPARE)) */
+
+#ifdef RT_ADC_USING_STREAM
+/**
+ * @brief Clear the FinSH stream scratch buffer owner.
+ * @param stream_device Pointer to the saved stream device owner.
+ */
+static void adc_msh_clear_stream_owner(rt_adc_device_t *stream_device)
+{
+ if (stream_device != RT_NULL)
+ {
+ *stream_device = RT_NULL;
+ }
+}
+
+/**
+ * @brief Get the current ADC framework state for the FinSH command.
+ * @param device Pointer to the ADC device object.
+ * @return Current ADC runtime state.
+ */
+static enum rt_adc_state adc_msh_get_state(rt_adc_device_t device)
+{
+ return (enum rt_adc_state)rt_atomic_load(&device->state);
+}
+
+/**
+ * @brief Check whether the FinSH stream scratch buffer is still owned by an active stream.
+ * @param stream_device Saved stream device owner.
+ * @return RT_TRUE if the stream buffer is still owned by an active stream.
+ */
+static rt_bool_t adc_msh_stream_buffer_is_active(rt_adc_device_t stream_device)
+{
+ if (stream_device == RT_NULL)
+ {
+ return RT_FALSE;
+ }
+
+ return (adc_msh_get_state(stream_device) == RT_ADC_STATE_STREAM) ? RT_TRUE : RT_FALSE;
+}
+#endif /* RT_ADC_USING_STREAM */
+
+static rt_adc_device_t device = RT_NULL;
+#ifdef RT_ADC_USING_STREAM
+static rt_adc_device_t stream_device = RT_NULL;
+static rt_uint32_t stream_dma_buffer[RT_ADC_MSH_STREAM_DMA_BUFFER_SIZE];
+#if defined(RT_ADC_MSH_USING_FIFO_TEST) && defined(RT_ADC_STREAM_USING_FIFO)
+static rt_uint32_t stream_fifo_buffer[RT_ADC_MSH_FIFO_TEST_FIFO_BUFFER_SIZE];
+#endif /* defined(RT_ADC_MSH_USING_FIFO_TEST) && defined(RT_ADC_STREAM_USING_FIFO) */
+#endif /* RT_ADC_USING_STREAM */
+
+
+#if defined(RT_ADC_USING_TRIGGER)
+/**
+ * @brief Handle ADC trigger subcommands.
+ * @param device Pointer to the selected ADC device object.
+ * @param argc Argument count excluding "adc trigger".
+ * @param argv Argument vector excluding "adc trigger".
+ * @return Command status.
+ */
+static rt_err_t adc_msh_trigger(rt_adc_device_t device, int argc, char **argv)
+{
+ rt_err_t result;
+
+ if (argc < 1)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ if (!rt_strcmp(argv[0], "clear"))
+ {
+ if (argc != 1)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ result = rt_adc_trigger_clear(device);
+ if (result == RT_EOK)
+ {
+ rt_kprintf("adc trigger cleared\n");
+ }
+ return result;
+ }
+
+ if (!rt_strcmp(argv[0], "status"))
+ {
+ if (argc != 1)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ rt_kprintf("adc trigger: %s\n", rt_adc_trigger_is_set(device) == RT_TRUE ? "set" : "clear");
+ return RT_EOK;
+ }
+
+ if (!rt_strcmp(argv[0], "set"))
+ {
+ struct rt_adc_trigger_cfg trigger_cfg;
+
+ if (argc < 2)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ rt_memset(&trigger_cfg, 0, sizeof(trigger_cfg));
+#if defined(RT_ADC_TRIGGER_USING_TIMER)
+ if (!rt_strcmp(argv[1], "timer_update"))
+ {
+ unsigned long freq_hz;
+
+ if (argc != 4)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ result = adc_msh_find_trigger_source(argv[2], &trigger_cfg.event.timer.timer);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ result = adc_msh_parse_ulong(argv[3], 0xffffffffUL, &freq_hz);
+ if ((result != RT_EOK) || (freq_hz == 0UL))
+ {
+ rt_kprintf("invalid adc trigger timer frequency: %s\n", argv[3]);
+ return (result != RT_EOK) ? result : -RT_EINVAL;
+ }
+
+ trigger_cfg.type = RT_ADC_TRIGGER_TIMER_UPDATE;
+ trigger_cfg.event.timer.freq_hz = (rt_uint32_t)freq_hz;
+ trigger_cfg.event.timer.channel = 0U;
+ }
+ else if (!rt_strcmp(argv[1], "timer_compare"))
+ {
+ unsigned long channel;
+ unsigned long freq_hz;
+
+ if (argc != 5)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ result = adc_msh_find_trigger_source(argv[2], &trigger_cfg.event.timer.timer);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ result = adc_msh_parse_ulong(argv[3], 65535UL, &channel);
+ if ((result != RT_EOK) || (channel == 0UL))
+ {
+ rt_kprintf("invalid adc trigger timer channel: %s\n", argv[3]);
+ return (result != RT_EOK) ? result : -RT_EINVAL;
+ }
+
+ result = adc_msh_parse_ulong(argv[4], 0xffffffffUL, &freq_hz);
+ if ((result != RT_EOK) || (freq_hz == 0UL))
+ {
+ rt_kprintf("invalid adc trigger timer frequency: %s\n", argv[4]);
+ return (result != RT_EOK) ? result : -RT_EINVAL;
+ }
+
+ trigger_cfg.type = RT_ADC_TRIGGER_TIMER_COMPARE;
+ trigger_cfg.event.timer.freq_hz = (rt_uint32_t)freq_hz;
+ trigger_cfg.event.timer.channel = (rt_uint16_t)channel;
+ }
+ else
+#endif /* defined(RT_ADC_TRIGGER_USING_TIMER) */
+#if defined(RT_ADC_TRIGGER_USING_COMPARE)
+ if (!rt_strcmp(argv[1], "compare"))
+ {
+ unsigned long channel;
+ int channel_arg;
+ int edge_arg;
+
+ if ((argc != 4) && (argc != 5))
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ if (argc == 5)
+ {
+ result = adc_msh_find_trigger_source(argv[2], &trigger_cfg.event.compare.comparator);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ channel_arg = 3;
+ edge_arg = 4;
+ }
+ else
+ {
+ channel_arg = 2;
+ edge_arg = 3;
+ }
+
+ result = adc_msh_parse_ulong(argv[channel_arg], 65535UL, &channel);
+ if ((result != RT_EOK) || (channel == 0UL))
+ {
+ rt_kprintf("invalid adc trigger compare channel: %s\n", argv[channel_arg]);
+ return (result != RT_EOK) ? result : -RT_EINVAL;
+ }
+
+ result = adc_msh_parse_trigger_edge(argv[edge_arg], &trigger_cfg.event.compare.edge);
+ if (result != RT_EOK)
+ {
+ rt_kprintf("invalid adc trigger edge: %s\n", argv[edge_arg]);
+ return result;
+ }
+
+ trigger_cfg.type = RT_ADC_TRIGGER_ANALOG_COMPARE;
+ trigger_cfg.event.compare.channel = (rt_uint16_t)channel;
+ }
+ else
+#endif /* defined(RT_ADC_TRIGGER_USING_COMPARE) */
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ result = rt_adc_trigger_set(device, &trigger_cfg);
+ if (result == RT_EOK)
+ {
+ rt_kprintf("adc trigger set: %s\n", argv[1]);
+ }
+ return result;
+ }
+
+ adc_msh_usage();
+ return -RT_EINVAL;
+}
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+#ifdef RT_ADC_USING_STREAM
+/**
+ * @brief Handle ADC stream subcommands.
+ * @param device Pointer to the selected ADC device object.
+ * @param argc Argument count excluding "adc stream".
+ * @param argv Argument vector excluding "adc stream".
+ * @return Command status.
+ */
+static rt_err_t adc_msh_stream(rt_adc_device_t device, int argc, char **argv)
+{
+ rt_uint32_t values[RT_ADC_MSH_VALUE_BUFFER_SIZE];
+ rt_size_t count;
+ rt_size_t index;
+ rt_err_t result;
+
+ if (argc < 1)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ if (!rt_strcmp(argv[0], "start"))
+ {
+#ifndef RT_ADC_STREAM_USING_LATEST
+ return -RT_ENOSYS;
+#else
+ struct rt_adc_stream_cfg stream_cfg;
+ rt_uint32_t channels;
+ rt_uint32_t channel_mask;
+
+ if (argc < 2)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ if (adc_msh_get_state(device) == RT_ADC_STATE_STREAM)
+ {
+ rt_kprintf("adc stream is already running, stop it first\n");
+ return -RT_EBUSY;
+ }
+
+ channels = 0U;
+ for (index = 0U; index < (rt_size_t)(argc - 1); index++)
+ {
+ result = adc_msh_parse_channel_mask(argv[index + 1], &channel_mask);
+ if (result != RT_EOK)
+ {
+ rt_kprintf("invalid adc channel: %s\n", argv[index + 1]);
+ return result;
+ }
+ channels |= channel_mask;
+ }
+
+ count = rt_adc_channel_mask_count(channels);
+ if (count == 0U)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (adc_msh_stream_buffer_is_active(stream_device) == RT_TRUE)
+ {
+ rt_kprintf("adc stream is already running, stop it first\n");
+ return -RT_EBUSY;
+ }
+ adc_msh_clear_stream_owner(&stream_device);
+
+ if (count > RT_ADC_MSH_STREAM_DMA_BUFFER_SIZE)
+ {
+ rt_kprintf("adc stream buffer too large, max %u samples\n", (unsigned int)RT_ADC_MSH_STREAM_DMA_BUFFER_SIZE);
+ return -RT_EINVAL;
+ }
+
+ rt_memset(&stream_cfg, 0, sizeof(stream_cfg));
+ stream_cfg.policy = RT_ADC_STREAM_POLICY_LATEST;
+ stream_cfg.dma_event_mode = RT_ADC_STREAM_DMA_EVENT_AUTO;
+ stream_cfg.dma_buffer = stream_dma_buffer;
+ stream_cfg.dma_buffer_length = count;
+
+ result = rt_adc_stream_start(device, channels, &stream_cfg);
+ if (result == RT_EOK)
+ {
+ stream_device = device;
+ }
+ else if (adc_msh_get_state(device) == RT_ADC_STATE_STREAM)
+ {
+ stream_device = device;
+ rt_kprintf("adc stream start failed but stream hardware may still be running, stop it first\n");
+ }
+ else
+ {
+ adc_msh_clear_stream_owner(&stream_device);
+ }
+ return result;
+#endif /* RT_ADC_STREAM_USING_LATEST */
+ }
+
+ if (!rt_strcmp(argv[0], "read"))
+ {
+ rt_ssize_t read_count;
+ rt_int32_t timeout_ms;
+
+ if (argc > 2)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ if ((stream_device != RT_NULL) && (stream_device != device))
+ {
+ rt_kprintf("adc stream belongs to another device\n");
+ return -RT_EBUSY;
+ }
+
+ if (adc_msh_get_state(device) != RT_ADC_STATE_STREAM)
+ {
+ rt_kprintf("adc stream is not running\n");
+ return -RT_EINVAL;
+ }
+
+ count = device->stream_ctrl.frame_length;
+ if (count == 0U)
+ {
+ rt_kprintf("adc stream channel count is zero\n");
+ return -RT_EINVAL;
+ }
+
+ if (count > RT_ADC_MSH_VALUE_BUFFER_SIZE)
+ {
+ rt_kprintf("adc stream read count too large, max %u samples\n", (unsigned int)RT_ADC_MSH_VALUE_BUFFER_SIZE);
+ return -RT_EINVAL;
+ }
+
+ timeout_ms = (argc == 2) ? adc_msh_parse_timeout(argv[1]) : RT_ADC_MSH_STREAM_READ_TIMEOUT_MS;
+ read_count = rt_adc_stream_read(device, values, count, timeout_ms);
+ if (read_count > 0)
+ {
+ for (index = 0U; index < (rt_size_t)read_count; index++)
+ {
+ rt_kprintf("[%u]=%lu\n", (unsigned int)index, (unsigned long)values[index]);
+ }
+ }
+
+ return (read_count >= 0) ? RT_EOK : (rt_err_t)read_count;
+ }
+
+ if (!rt_strcmp(argv[0], "stop"))
+ {
+ if (argc != 1)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ if ((stream_device != RT_NULL) && (stream_device != device))
+ {
+ rt_kprintf("adc stream belongs to another device\n");
+ return -RT_EBUSY;
+ }
+
+ result = rt_adc_stream_stop(device);
+ if (result == RT_EOK)
+ {
+ adc_msh_clear_stream_owner(&stream_device);
+ }
+ return result;
+ }
+
+ if (!rt_strcmp(argv[0], "cancel"))
+ {
+ if (argc != 1)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ if ((stream_device != RT_NULL) && (stream_device != device))
+ {
+ rt_kprintf("adc stream belongs to another device\n");
+ return -RT_EBUSY;
+ }
+
+ return rt_adc_stream_cancel(device);
+ }
+
+ adc_msh_usage();
+ return -RT_EINVAL;
+}
+#endif /* RT_ADC_USING_STREAM */
+
+#if defined(RT_ADC_USING_STREAM) && defined(RT_ADC_MSH_USING_FIFO_TEST) && defined(RT_ADC_STREAM_USING_FIFO)
+#define RT_ADC_MSH_FIFO_TEST_READ_FRAME_COUNT 16U
+#define RT_ADC_MSH_FIFO_TEST_READ_TIMEOUT_MS 1000
+/**
+ * @brief Handle the ADC FIFO stream test command.
+ * @param device Pointer to the selected ADC device object.
+ * @param argc Argument count excluding "adc fifo".
+ * @param argv Argument vector excluding "adc fifo".
+ * @return Command status.
+ */
+static rt_err_t adc_msh_fifo_test(rt_adc_device_t device, int argc, char **argv)
+{
+ struct rt_adc_stream_cfg stream_cfg;
+ rt_uint32_t *buffer;
+ rt_uint32_t channels;
+ rt_uint32_t channel_mask;
+ rt_size_t channel_count;
+ rt_size_t sample_count;
+ rt_size_t dma_buffer_length;
+ rt_size_t fifo_buffer_length;
+ rt_size_t read_times;
+ rt_size_t index;
+ rt_size_t full_count;
+ rt_size_t partial_count;
+ rt_size_t timeout_count;
+ rt_size_t error_count;
+ rt_size_t total_samples;
+ rt_tick_t begin_tick;
+ rt_tick_t end_tick;
+ rt_tick_t timeout_tick;
+ rt_int32_t timeout_ms;
+ rt_err_t result;
+ rt_err_t stop_result;
+
+ if (argc < 2)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ timeout_ms = adc_msh_parse_timeout(argv[0]);
+ if (timeout_ms <= 0)
+ {
+ rt_kprintf("fifo test timeout_ms must be greater than 0\n");
+ return -RT_EINVAL;
+ }
+
+ if (adc_msh_get_state(device) == RT_ADC_STATE_STREAM)
+ {
+ rt_kprintf("adc stream is already running, stop it first\n");
+ return -RT_EBUSY;
+ }
+
+ if (adc_msh_stream_buffer_is_active(stream_device) == RT_TRUE)
+ {
+ rt_kprintf("adc stream is already running, stop it first\n");
+ return -RT_EBUSY;
+ }
+ adc_msh_clear_stream_owner(&stream_device);
+
+ channels = 0U;
+ for (index = 0U; index < (rt_size_t)(argc - 1); index++)
+ {
+ result = adc_msh_parse_channel_mask(argv[index + 1], &channel_mask);
+ if (result != RT_EOK)
+ {
+ rt_kprintf("invalid adc channel: %s\n", argv[index + 1]);
+ return result;
+ }
+ channels |= channel_mask;
+ }
+
+ channel_count = rt_adc_channel_mask_count(channels);
+ if (channel_count == 0U)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (RT_ADC_MSH_STREAM_DMA_BUFFER_SIZE < (channel_count * 2U))
+ {
+ rt_kprintf("adc fifo test dma buffer too small, min %u samples\n", (unsigned int)(channel_count * 2U));
+ return -RT_EINVAL;
+ }
+
+ sample_count = channel_count * RT_ADC_MSH_FIFO_TEST_READ_FRAME_COUNT;
+ if ((sample_count == 0U) || (sample_count > (((rt_size_t)-1) / sizeof(rt_uint32_t))))
+ {
+ rt_kprintf("adc fifo test read sample count is too large\n");
+ return -RT_EINVAL;
+ }
+
+ dma_buffer_length = (RT_ADC_MSH_STREAM_DMA_BUFFER_SIZE / (channel_count * 2U)) * (channel_count * 2U);
+ fifo_buffer_length = (RT_ADC_MSH_FIFO_TEST_FIFO_BUFFER_SIZE / channel_count) * channel_count;
+ if (fifo_buffer_length < (dma_buffer_length / 2U))
+ {
+ rt_kprintf("adc fifo test fifo buffer too small, min %u samples\n", (unsigned int)(dma_buffer_length / 2U));
+ return -RT_EINVAL;
+ }
+
+ buffer = (rt_uint32_t *)rt_malloc(sample_count * sizeof(rt_uint32_t));
+ if (buffer == RT_NULL)
+ {
+ rt_kprintf("no memory for fifo test buffer\n");
+ return -RT_ENOMEM;
+ }
+
+ rt_memset(&stream_cfg, 0, sizeof(stream_cfg));
+ stream_cfg.policy = RT_ADC_STREAM_POLICY_FIFO;
+ stream_cfg.dma_event_mode = RT_ADC_STREAM_DMA_EVENT_HALF_FULL;
+ stream_cfg.dma_buffer = stream_dma_buffer;
+ stream_cfg.dma_buffer_length = dma_buffer_length;
+ stream_cfg.fifo_buffer = stream_fifo_buffer;
+ stream_cfg.fifo_buffer_length = fifo_buffer_length;
+ stream_cfg.watermark = dma_buffer_length / 2U;
+
+ result = rt_adc_stream_start_frame_aligned_fifo(device, channels, &stream_cfg);
+ if (result != RT_EOK)
+ {
+ rt_free(buffer);
+ return result;
+ }
+ stream_device = device;
+
+ result = RT_EOK;
+ read_times = 0U;
+ full_count = 0U;
+ partial_count = 0U;
+ timeout_count = 0U;
+ error_count = 0U;
+ total_samples = 0U;
+ begin_tick = rt_tick_get();
+ timeout_tick = rt_tick_from_millisecond(timeout_ms);
+
+ while (rt_tick_get_delta(begin_tick) < timeout_tick)
+ {
+ rt_ssize_t read_count;
+
+ read_count = rt_adc_stream_read(device, buffer, sample_count, RT_ADC_MSH_FIFO_TEST_READ_TIMEOUT_MS);
+ read_times++;
+ if (read_count < 0)
+ {
+ if (read_count == -RT_ETIMEOUT)
+ {
+ timeout_count++;
+ continue;
+ }
+
+ result = (rt_err_t)read_count;
+ error_count++;
+ rt_kprintf("stream fifo test failed at %u: %d\n", (unsigned int)read_times, (int)read_count);
+ break;
+ }
+
+ total_samples += (rt_size_t)read_count;
+ if ((rt_size_t)read_count == sample_count)
+ {
+ full_count++;
+ }
+ else
+ {
+ partial_count++;
+ }
+ }
+
+ end_tick = rt_tick_get();
+ stop_result = rt_adc_stream_stop(device);
+ if (stop_result == RT_EOK)
+ {
+ adc_msh_clear_stream_owner(&stream_device);
+ }
+
+ rt_size_t elapsed_tick;
+ rt_size_t total_frames;
+ rt_size_t read_frames;
+ rt_size_t dma_frames;
+ rt_size_t fifo_frames;
+ rt_size_t frame_rate;
+ rt_size_t sample_rate_per_channel;
+
+ elapsed_tick = end_tick - begin_tick;
+ total_frames = total_samples / channel_count;
+ read_frames = sample_count / channel_count;
+ dma_frames = dma_buffer_length / channel_count;
+ fifo_frames = fifo_buffer_length / channel_count;
+
+ if (elapsed_tick > 0U)
+ {
+ frame_rate = (total_frames * RT_TICK_PER_SECOND) / elapsed_tick;
+ sample_rate_per_channel = frame_rate;
+ }
+ else
+ {
+ frame_rate = 0U;
+ sample_rate_per_channel = 0U;
+ }
+
+ rt_kprintf("adc fifo_test: %s\n", (error_count == 0U && timeout_count == 0U && partial_count == 0U) ? "PASS" : "FAIL");
+ rt_kprintf(" duration : %d ms\n", (int)timeout_ms);
+ rt_kprintf(" channels : %u\n", (unsigned int)channel_count);
+ rt_kprintf(" read block : %u frames, %u samples\n",
+ (unsigned int)read_frames, (unsigned int)sample_count);
+ rt_kprintf(" dma buffer : %u frames, %u samples\n",
+ (unsigned int)dma_frames, (unsigned int)dma_buffer_length);
+ rt_kprintf(" fifo buffer : %u frames, %u samples\n",
+ (unsigned int)fifo_frames, (unsigned int)fifo_buffer_length);
+ rt_kprintf(" read count : %u\n", (unsigned int)read_times);
+ rt_kprintf(" full reads : %u\n", (unsigned int)full_count);
+ rt_kprintf(" partial reads : %u\n", (unsigned int)partial_count);
+ rt_kprintf(" timeouts : %u\n", (unsigned int)timeout_count);
+ rt_kprintf(" errors : %u\n", (unsigned int)error_count);
+ rt_kprintf(" total frames : %u\n", (unsigned int)total_frames);
+ rt_kprintf(" total samples : %u\n", (unsigned int)total_samples);
+ rt_kprintf(" frame rate : %u frames/s\n", (unsigned int)frame_rate);
+ rt_kprintf(" sample rate/ch : %u samples/s\n", (unsigned int)sample_rate_per_channel);
+ rt_kprintf(" elapsed tick : %u\n", (unsigned int)elapsed_tick);
+
+ rt_free(buffer);
+ if (error_count > 0U)
+ {
+ return result;
+ }
+
+ if (timeout_count > 0U)
+ {
+ return -RT_ETIMEOUT;
+ }
+
+ return stop_result;
+}
+#endif /* defined(RT_ADC_USING_STREAM) && defined(RT_ADC_MSH_USING_FIFO_TEST) && defined(RT_ADC_STREAM_USING_FIFO) */
+
+/**
+ * @brief Handle the ADC FinSH command.
+ * @param argc Argument count.
+ * @param argv Argument vector.
+ * @return Command status.
+ */
+static int adc(int argc, char **argv)
+{
+ rt_uint32_t values_buf[RT_ADC_MSH_VALUE_BUFFER_SIZE];
+ rt_uint32_t *values;
+ rt_uint32_t channels;
+ rt_uint32_t channel_mask;
+ rt_size_t index;
+ rt_size_t count;
+ rt_ssize_t read_count;
+ rt_err_t result;
+
+ if (argc < 2)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ values = values_buf;
+ if (!rt_strcmp(argv[1], "probe"))
+ {
+ rt_device_t new_dev;
+ rt_adc_device_t new_device;
+
+ if (argc != 3)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+#ifdef RT_ADC_USING_STREAM
+ if (adc_msh_stream_buffer_is_active(stream_device) == RT_TRUE)
+ {
+ rt_kprintf("adc stream is running, stop it first\n");
+ return -RT_EBUSY;
+ }
+ adc_msh_clear_stream_owner(&stream_device);
+#endif /* RT_ADC_USING_STREAM */
+
+ new_dev = rt_device_find(argv[2]);
+ if (new_dev == RT_NULL)
+ {
+ rt_kprintf("adc device %s not found\n", argv[2]);
+ return -RT_ERROR;
+ }
+
+ if (new_dev->type != RT_Device_Class_ADC)
+ {
+ rt_kprintf("%s is not an adc device\n", argv[2]);
+ return -RT_EINVAL;
+ }
+
+ new_device = (rt_adc_device_t)new_dev;
+ if (device == new_device)
+ {
+ rt_kprintf("probe %s: %d\n", argv[2], RT_EOK);
+ return RT_EOK;
+ }
+
+ result = rt_device_open(new_dev, RT_DEVICE_OFLAG_RDWR);
+ if (result != RT_EOK)
+ {
+ rt_kprintf("probe %s failed: %d\n", argv[2], result);
+ return result;
+ }
+
+ if (device != RT_NULL)
+ {
+ rt_err_t close_result;
+
+ close_result = rt_device_close(&device->parent);
+ if (close_result != RT_EOK)
+ {
+ (void)rt_device_close(new_dev);
+ rt_kprintf("close previous adc device %s failed: %d\n", device->parent.parent.name, close_result);
+ return close_result;
+ }
+ }
+
+ device = new_device;
+ rt_kprintf("probe %s: %d\n", argv[2], result);
+ return result;
+ }
+
+ if (device == RT_NULL)
+ {
+ rt_kprintf("no adc device selected\n");
+ return -RT_EINVAL;
+ }
+
+ if (!rt_strcmp(argv[1], "config"))
+ {
+ if (argc < 3)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ channels = 0U;
+ for (index = 0U; index < (rt_size_t)(argc - 2); index++)
+ {
+ result = adc_msh_parse_channel_mask(argv[index + 2], &channel_mask);
+ if (result != RT_EOK)
+ {
+ rt_kprintf("invalid adc channel: %s\n", argv[index + 2]);
+ return result;
+ }
+ channels |= channel_mask;
+ }
+
+ result = rt_device_control(&device->parent, RT_ADC_CMD_SET_SESSION, &channels);
+ if (result == RT_EOK)
+ {
+ rt_kprintf("config channels=0x%08lx count=%u\n",
+ (unsigned long)channels, (unsigned int)rt_adc_channel_mask_count(channels));
+ }
+ return result;
+ }
+
+ if (!rt_strcmp(argv[1], "read"))
+ {
+ if (argc > 2)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ if ((device->session_ctrl.configured != RT_TRUE) || (device->session_ctrl.channel_count == 0U))
+ {
+ rt_kprintf("adc session is not configured, run adc config first\n");
+ return -RT_EINVAL;
+ }
+
+ count = device->session_ctrl.channel_count;
+ if (count > RT_ADC_MSH_VALUE_BUFFER_SIZE)
+ {
+ rt_kprintf("adc read count too large, max %u samples\n", (unsigned int)RT_ADC_MSH_VALUE_BUFFER_SIZE);
+ return -RT_EINVAL;
+ }
+
+ read_count = rt_device_read(&device->parent, 0, values, count);
+ if (read_count < 0)
+ {
+ result = (rt_err_t)read_count;
+ }
+ else if ((rt_size_t)read_count != count)
+ {
+ result = -RT_ERROR;
+ }
+ else
+ {
+ result = RT_EOK;
+ for (index = 0U; index < count; index++)
+ {
+ rt_kprintf("[%u]=%lu\n", (unsigned int)index, (unsigned long)values[index]);
+ }
+ }
+ return result;
+ }
+
+ if (!rt_strcmp(argv[1], "voltage"))
+ {
+ rt_int32_t timeout_ms;
+
+ if (argc != 3)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ if ((device->session_ctrl.configured != RT_TRUE) || (device->session_ctrl.channel_count == 0U))
+ {
+ rt_kprintf("adc session is not configured, run adc config first\n");
+ return -RT_EINVAL;
+ }
+
+ timeout_ms = adc_msh_parse_timeout(argv[2]);
+ count = device->session_ctrl.channel_count;
+ if (count > RT_ADC_MSH_VALUE_BUFFER_SIZE)
+ {
+ rt_kprintf("adc voltage count too large, max %u samples\n", (unsigned int)RT_ADC_MSH_VALUE_BUFFER_SIZE);
+ return -RT_EINVAL;
+ }
+
+ result = rt_adc_voltage(device, values, count, timeout_ms);
+ if (result == RT_EOK)
+ {
+ for (index = 0U; index < count; index++)
+ {
+ rt_kprintf("[%u]=%lu mV\n", (unsigned int)index, (unsigned long)values[index]);
+ }
+ }
+ else if (result == -RT_EINVAL)
+ {
+ rt_kprintf("adc voltage failed, check adc config and VREF channel/default_vref\n");
+ }
+
+ return result;
+ }
+
+ if (!rt_strcmp(argv[1], "seq"))
+ {
+ struct rt_adc_sequence_cfg sequence_cfg;
+ rt_size_t sequence_read_count;
+
+ if (argc < 4)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ channels = 0U;
+ for (index = 0U; index < (rt_size_t)(argc - 3); index++)
+ {
+ result = adc_msh_parse_channel_mask(argv[index + 3], &channel_mask);
+ if (result != RT_EOK)
+ {
+ rt_kprintf("invalid adc channel: %s\n", argv[index + 3]);
+ return result;
+ }
+ channels |= channel_mask;
+ }
+
+ count = rt_adc_channel_mask_count(channels);
+ if (count > RT_ADC_MSH_VALUE_BUFFER_SIZE)
+ {
+ rt_kprintf("adc seq count too large, max %u samples\n", (unsigned int)RT_ADC_MSH_VALUE_BUFFER_SIZE);
+ return -RT_EINVAL;
+ }
+
+ rt_memset(&sequence_cfg, 0, sizeof(sequence_cfg));
+ sequence_cfg.buffer = values;
+ sequence_cfg.buffer_length = count;
+ sequence_cfg.timeout_ms = adc_msh_parse_timeout(argv[2]);
+
+ result = rt_adc_read_sequence(device, channels, &sequence_cfg, &sequence_read_count);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ for (index = 0U; index < sequence_read_count; index++)
+ {
+ rt_kprintf("[%u]=%lu\n", (unsigned int)index, (unsigned long)values[index]);
+ }
+
+ return RT_EOK;
+ }
+
+#if defined(RT_ADC_USING_TRIGGER)
+ if (!rt_strcmp(argv[1], "trigger"))
+ {
+ return adc_msh_trigger(device, argc - 2, &argv[2]);
+ }
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+ if (!rt_strcmp(argv[1], "close"))
+ {
+ rt_err_t close_result;
+
+ if (argc > 2)
+ {
+ adc_msh_usage();
+ return -RT_EINVAL;
+ }
+
+ close_result = rt_device_close(&device->parent);
+ if (close_result != RT_EOK)
+ {
+ rt_kprintf("close adc device %s failed: %d\n", device->parent.parent.name, close_result);
+ return close_result;
+ }
+
+ rt_kprintf("close %s: %d\n", device->parent.parent.name, close_result);
+ device = RT_NULL;
+ return RT_EOK;
+ }
+
+#ifdef RT_ADC_USING_STREAM
+ if (!rt_strcmp(argv[1], "stream"))
+ {
+ return adc_msh_stream(device, argc - 2, &argv[2]);
+ }
+
+#if defined(RT_ADC_MSH_USING_FIFO_TEST) && defined(RT_ADC_STREAM_USING_FIFO)
+ if (!rt_strcmp(argv[1], "fifo"))
+ {
+ return adc_msh_fifo_test(device, argc - 2, &argv[2]);
+ }
+#endif /* defined(RT_ADC_MSH_USING_FIFO_TEST) && defined(RT_ADC_STREAM_USING_FIFO) */
+#endif /* RT_ADC_USING_STREAM */
+
+ result = rt_adc_msh_special(device, argc - 1, &argv[1]);
+ if (result != -RT_ENOSYS)
+ {
+ return result;
+ }
+
+ adc_msh_usage();
+ return -RT_EINVAL;
+}
+MSH_CMD_EXPORT(adc, adc v2 command);
+#endif /* defined(RT_USING_ADC_V2) && defined(RT_USING_FINSH) && defined(RT_ADC_V2_USING_MSH) */
diff --git a/components/drivers/adc/adc_v2_trigger.c b/components/drivers/adc/adc_v2_trigger.c
new file mode 100644
index 0000000000..0521201c49
--- /dev/null
+++ b/components/drivers/adc/adc_v2_trigger.c
@@ -0,0 +1,411 @@
+/*
+ * Copyright (c) 2006-2026, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-05-20 wdfk-prog add ADC V2 timer trigger framework implementation
+ */
+
+/**
+ * @file adc_v2_trigger.c
+ * @brief ADC V2 trigger framework implementation.
+ */
+
+#include <rtconfig.h>
+
+#if defined(RT_USING_ADC_V2) && defined(RT_ADC_USING_TRIGGER)
+
+#include <rtdevice.h>
+#include <rtthread.h>
+#if defined(RT_ADC_TRIGGER_USING_TIMER) && defined(RT_USING_CLOCK_TIMER_TRIGGER)
+#include <drivers/clock_time.h>
+#endif /* defined(RT_ADC_TRIGGER_USING_TIMER) && defined(RT_USING_CLOCK_TIMER_TRIGGER) */
+
+#define DBG_TAG "adc.v2.trigger"
+#define DBG_LVL DBG_LOG
+#include <rtdbg.h>
+
+#include "adc_v2_internal.h"
+
+/**
+ * @brief Validate an ADC hardware trigger configuration at framework level.
+ * @param cfg Pointer to the trigger configuration object.
+ * @return Operation status.
+ */
+static rt_err_t adc_validate_trigger_cfg_common(const struct rt_adc_trigger_cfg *cfg)
+{
+ switch (cfg->type)
+ {
+ case RT_ADC_TRIGGER_TIMER_UPDATE:
+#if defined(RT_ADC_TRIGGER_USING_TIMER)
+ if ((cfg->event.timer.timer == RT_NULL) || (cfg->event.timer.timer->type != RT_Device_Class_Timer) ||
+ (cfg->event.timer.freq_hz == 0U))
+ {
+ return -RT_EINVAL;
+ }
+ return RT_EOK;
+#else
+ return -RT_ENOSYS;
+#endif /* defined(RT_ADC_TRIGGER_USING_TIMER) */
+
+ case RT_ADC_TRIGGER_TIMER_COMPARE:
+#if defined(RT_ADC_TRIGGER_USING_TIMER)
+ if ((cfg->event.timer.timer == RT_NULL) || (cfg->event.timer.timer->type != RT_Device_Class_Timer) ||
+ (cfg->event.timer.channel == 0U) || (cfg->event.timer.freq_hz == 0U))
+ {
+ return -RT_EINVAL;
+ }
+ return RT_EOK;
+#else
+ return -RT_ENOSYS;
+#endif /* defined(RT_ADC_TRIGGER_USING_TIMER) */
+
+ case RT_ADC_TRIGGER_ANALOG_COMPARE:
+#if defined(RT_ADC_TRIGGER_USING_COMPARE)
+ if ((cfg->event.compare.channel == 0U) || (cfg->event.compare.edge == RT_ADC_TRIGGER_EDGE_NONE))
+ {
+ return -RT_EINVAL;
+ }
+ return RT_EOK;
+#else
+ return -RT_ENOSYS;
+#endif /* defined(RT_ADC_TRIGGER_USING_COMPARE) */
+
+ case RT_ADC_TRIGGER_PWM_EDGE:
+ case RT_ADC_TRIGGER_EXTI_EDGE:
+ case RT_ADC_TRIGGER_BACKEND:
+ return -RT_ENOSYS;
+
+ default:
+ return -RT_EINVAL;
+ }
+}
+
+/**
+ * @brief Initialize the ADC trigger control state to software-started mode.
+ * @param device Pointer to the ADC device object.
+ */
+void adc_trigger_init(rt_adc_device_t device)
+{
+ rt_memset(&device->trigger_ctrl, 0, sizeof(device->trigger_ctrl));
+}
+
+/**
+ * @brief Get the active device-level ADC trigger configuration.
+ * @param device Pointer to the ADC device object.
+ * @return Pointer to the cached trigger configuration, or RT_NULL when no hardware trigger is configured.
+ */
+const struct rt_adc_trigger_cfg *adc_trigger_active_get(rt_adc_device_t device)
+{
+ if (device->trigger_ctrl.configured != RT_TRUE)
+ {
+ return RT_NULL;
+ }
+
+ return &device->trigger_ctrl.cfg;
+}
+
+#if defined(RT_ADC_TRIGGER_USING_TIMER)
+/**
+ * @brief Configure and start a timer-based ADC trigger source.
+ * @param cfg Pointer to the active ADC trigger configuration object.
+ * @return Operation status.
+ */
+static rt_err_t adc_trigger_timer_source_start(const struct rt_adc_trigger_cfg *cfg)
+{
+#if defined(RT_USING_CLOCK_TIMER_TRIGGER)
+ struct rt_clock_timer_trigger_cfg timer_cfg = {0};
+ rt_err_t result;
+
+ timer_cfg.freq_hz = cfg->event.timer.freq_hz;
+ timer_cfg.channel = cfg->event.timer.channel;
+
+ switch (cfg->type)
+ {
+ case RT_ADC_TRIGGER_TIMER_UPDATE:
+ timer_cfg.event = CLOCK_TIMER_TRIGGER_EVENT_UPDATE;
+ timer_cfg.channel = 0U;
+ break;
+
+ case RT_ADC_TRIGGER_TIMER_COMPARE:
+ timer_cfg.event = CLOCK_TIMER_TRIGGER_EVENT_COMPARE;
+ break;
+
+ default:
+ return -RT_ENOSYS;
+ }
+
+ result = rt_device_control(cfg->event.timer.timer, CLOCK_TIMER_CTRL_TRIGGER_CONFIG, &timer_cfg);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ result = rt_device_control(cfg->event.timer.timer, CLOCK_TIMER_CTRL_TRIGGER_START, RT_NULL);
+ if (result != RT_EOK)
+ {
+ (void)rt_device_control(cfg->event.timer.timer, CLOCK_TIMER_CTRL_TRIGGER_RELEASE, RT_NULL);
+ return result;
+ }
+
+ return RT_EOK;
+#else
+ RT_UNUSED(cfg);
+ return -RT_ENOSYS;
+#endif /* defined(RT_USING_CLOCK_TIMER_TRIGGER) */
+}
+
+/**
+ * @brief Stop and release a timer-based ADC trigger source.
+ * @param cfg Pointer to the active ADC trigger configuration object.
+ * @return Operation status.
+ */
+static rt_err_t adc_trigger_timer_source_stop(const struct rt_adc_trigger_cfg *cfg)
+{
+#if defined(RT_USING_CLOCK_TIMER_TRIGGER)
+ rt_err_t stop_result;
+ rt_err_t release_result;
+
+ if ((cfg->type != RT_ADC_TRIGGER_TIMER_UPDATE) && (cfg->type != RT_ADC_TRIGGER_TIMER_COMPARE))
+ {
+ return -RT_ENOSYS;
+ }
+
+ stop_result = rt_device_control(cfg->event.timer.timer, CLOCK_TIMER_CTRL_TRIGGER_STOP, RT_NULL);
+ release_result = rt_device_control(cfg->event.timer.timer, CLOCK_TIMER_CTRL_TRIGGER_RELEASE, RT_NULL);
+
+ return (release_result != RT_EOK) ? release_result : stop_result;
+#else
+ RT_UNUSED(cfg);
+ return -RT_ENOSYS;
+#endif /* defined(RT_USING_CLOCK_TIMER_TRIGGER) */
+}
+#endif /* defined(RT_ADC_TRIGGER_USING_TIMER) */
+
+/**
+ * @brief Start the active trigger source after the ADC conversion path is armed.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ */
+static rt_err_t adc_trigger_source_start(rt_adc_device_t device)
+{
+ const struct rt_adc_trigger_cfg *cfg;
+
+ cfg = adc_trigger_active_get(device);
+ if (cfg == RT_NULL)
+ {
+ return RT_EOK;
+ }
+
+ switch (cfg->type)
+ {
+#if defined(RT_ADC_TRIGGER_USING_TIMER)
+ case RT_ADC_TRIGGER_TIMER_UPDATE:
+ case RT_ADC_TRIGGER_TIMER_COMPARE:
+ return adc_trigger_timer_source_start(cfg);
+#endif /* defined(RT_ADC_TRIGGER_USING_TIMER) */
+
+ default:
+ return RT_EOK;
+ }
+}
+
+/**
+ * @brief Stop the active trigger source before the ADC conversion path is disarmed.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ */
+static rt_err_t adc_trigger_source_stop(rt_adc_device_t device)
+{
+ const struct rt_adc_trigger_cfg *cfg;
+
+ cfg = adc_trigger_active_get(device);
+ if (cfg == RT_NULL)
+ {
+ return RT_EOK;
+ }
+
+ switch (cfg->type)
+ {
+#if defined(RT_ADC_TRIGGER_USING_TIMER)
+ case RT_ADC_TRIGGER_TIMER_UPDATE:
+ case RT_ADC_TRIGGER_TIMER_COMPARE:
+ return adc_trigger_timer_source_stop(cfg);
+#endif /* defined(RT_ADC_TRIGGER_USING_TIMER) */
+
+ default:
+ return RT_EOK;
+ }
+}
+
+/**
+ * @brief Control the active trigger source around an armed ADC conversion path.
+ * @param device Pointer to the ADC device object.
+ * @param action Trigger-source lifecycle action.
+ * @param started Pointer to the trigger-start state.
+ * @param current_result Transfer result before trigger stop, ignored for start.
+ * @return For start, trigger-source start result; for stop, @p current_result unless trigger stop is the first
+ * observed failure.
+ */
+rt_err_t adc_trigger_source_control(rt_adc_device_t device, enum adc_trigger_source_action action,
+ rt_bool_t *started, rt_err_t current_result)
+{
+ rt_err_t stop_result;
+ rt_err_t result;
+
+ switch (action)
+ {
+ case ADC_TRIGGER_SOURCE_START:
+ *started = RT_FALSE;
+ if (adc_trigger_active_get(device) == RT_NULL)
+ {
+ return RT_EOK;
+ }
+
+ result = adc_trigger_source_start(device);
+ if (result != RT_EOK)
+ {
+ LOG_E("trigger source start failed: device=%s result=%d", device->parent.parent.name, result);
+ return result;
+ }
+
+ *started = RT_TRUE;
+ return RT_EOK;
+
+ case ADC_TRIGGER_SOURCE_STOP:
+ if (*started != RT_TRUE)
+ {
+ return current_result;
+ }
+
+ stop_result = adc_trigger_source_stop(device);
+ if (stop_result != RT_EOK)
+ {
+ LOG_E("trigger source stop failed: device=%s result=%d", device->parent.parent.name, stop_result);
+ if (current_result == RT_EOK)
+ {
+ current_result = stop_result;
+ }
+ }
+
+ *started = RT_FALSE;
+ return current_result;
+
+ default:
+ return (current_result != RT_EOK) ? current_result : -RT_EINVAL;
+ }
+}
+
+/**
+ * @brief Preconfigure the active device-level ADC trigger before hardware configuration.
+ * @param device Pointer to the ADC device object.
+ * @return RT_EOK on success, otherwise an RT-Thread error code.
+ *
+ * @note A null active trigger still has to be passed to the backend when the
+ * backend provides trigger_prepare(), because some backends cache the
+ * previously prepared hardware trigger selector. Passing RT_NULL asks the
+ * backend to prepare the software-start/default trigger path.
+ */
+rt_err_t adc_trigger_preconfig(rt_adc_device_t device)
+{
+ const struct rt_adc_trigger_cfg *cfg;
+
+ cfg = adc_trigger_active_get(device);
+
+ if ((device->ops == RT_NULL) || (device->ops->core == RT_NULL) || (device->ops->core->trigger_prepare == RT_NULL))
+ {
+ return (cfg == RT_NULL) ? RT_EOK : -RT_ENOSYS;
+ }
+
+ return device->ops->core->trigger_prepare(device, cfg);
+}
+
+/**
+ * @brief Set one device-level ADC hardware trigger request.
+ * @param device Pointer to the ADC device object.
+ * @param cfg Pointer to the ADC trigger configuration object.
+ * @return Operation status.
+ * @note This function only checks the framework-level trigger structure and
+ * caches the request. It does not write ADC trigger registers immediately.
+ * Backend compatibility is reported when the cached trigger is
+ * preconfigured before a stream hardware configuration.
+ */
+rt_err_t rt_adc_trigger_set(rt_adc_device_t device, const struct rt_adc_trigger_cfg *cfg)
+{
+ struct rt_adc_trigger_cfg trigger_cfg;
+ rt_err_t result;
+ rt_atomic_t old_state;
+
+ if ((device == RT_NULL) || (cfg == RT_NULL))
+ {
+ return -RT_EINVAL;
+ }
+
+ trigger_cfg = *cfg;
+ old_state = (rt_atomic_t)RT_ADC_STATE_IDLE;
+ if (rt_atomic_compare_exchange_strong(&device->state, &old_state, (rt_atomic_t)RT_ADC_STATE_LOCKED) == 0)
+ {
+ return -RT_EBUSY;
+ }
+
+ result = adc_validate_trigger_cfg_common(&trigger_cfg);
+ if (result == RT_EOK)
+ {
+ device->trigger_ctrl.cfg = trigger_cfg;
+ device->trigger_ctrl.configured = RT_TRUE;
+ }
+
+ rt_atomic_store(&device->state, (rt_atomic_t)RT_ADC_STATE_IDLE);
+ if (result != RT_EOK)
+ {
+ LOG_E("trigger set cache failed: device=%s result=%d", device->parent.parent.name, result);
+ }
+ return result;
+}
+
+/**
+ * @brief Clear the cached ADC trigger configuration.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ * @note The ADC device must be idle so the cached trigger cannot be cleared while an active conversion is using it.
+ */
+rt_err_t rt_adc_trigger_clear(rt_adc_device_t device)
+{
+ rt_atomic_t old_state;
+
+ if (device == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+
+ old_state = (rt_atomic_t)RT_ADC_STATE_IDLE;
+ if (rt_atomic_compare_exchange_strong(&device->state, &old_state, (rt_atomic_t)RT_ADC_STATE_LOCKED) == 0)
+ {
+ return -RT_EBUSY;
+ }
+
+ rt_memset(&device->trigger_ctrl, 0, sizeof(device->trigger_ctrl));
+ rt_atomic_store(&device->state, (rt_atomic_t)RT_ADC_STATE_IDLE);
+
+ LOG_D("trigger cleared: device=%s", device->parent.parent.name);
+ return RT_EOK;
+}
+
+/**
+ * @brief Check whether one ADC device has a cached hardware trigger request.
+ * @param device Pointer to the ADC device object.
+ * @return RT_TRUE if a hardware trigger request is set, otherwise RT_FALSE.
+ */
+rt_bool_t rt_adc_trigger_is_set(rt_adc_device_t device)
+{
+ if (device == RT_NULL)
+ {
+ return RT_FALSE;
+ }
+
+ return device->trigger_ctrl.configured;
+}
+
+#endif /* defined(RT_USING_ADC_V2) && defined(RT_ADC_USING_TRIGGER) */
diff --git a/components/drivers/clock_time/Kconfig b/components/drivers/clock_time/Kconfig
index d6259617aa..46dd271483 100644
--- a/components/drivers/clock_time/Kconfig
+++ b/components/drivers/clock_time/Kconfig
@@ -2,6 +2,17 @@ menuconfig RT_USING_CLOCK_TIME
bool "Clock time subsystem"
default n
+config RT_USING_CLOCK_TIMER_TRIGGER
+ bool "Enable clock timer hardware trigger output"
+ depends on RT_USING_CLOCK_TIME
+ default n
+ help
+ Enable clock timer hardware trigger output controls. Timer backends
+ may use this to expose update/TRGO trigger events to ADC drivers. The
+ selected timer must be dedicated to trigger output; the framework does
+ not arbitrate shared use with clock-time timeouts, PWM, or other timer
+ consumers.
+
if RT_USING_CLOCK_TIME
config CLOCK_TIMER_FREQ
int "Clock time timer freq"
diff --git a/components/drivers/include/drivers/adc_v2.h b/components/drivers/include/drivers/adc_v2.h
new file mode 100644
index 0000000000..bf369e3a50
--- /dev/null
+++ b/components/drivers/include/drivers/adc_v2.h
@@ -0,0 +1,622 @@
+/*
+ * Copyright (c) 2006-2026, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-04-21 wdfk-prog add standalone adc v2 framework interface
+ */
+
+/**
+ * @file adc_v2.h
+ * @brief Standalone ADC V2 framework declarations.
+ */
+
+#ifndef __ADC_V2_H__
+#define __ADC_V2_H__
+
+#include <rtconfig.h>
+#include "misc.h"
+#if !defined(RT_USING_ADC_V2)
+#error "ADC V2 interface requires RT_USING_ADC_V2."
+#endif /* !defined(RT_USING_ADC_V2) */
+
+#include <rtatomic.h>
+#include <rtdevice.h>
+#include <rtthread.h>
+
+#if defined(RT_ADC_USING_TRIGGER)
+#include <drivers/adc_v2_trigger.h>
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+#if defined(RT_ADC_USING_STREAM) && defined(RT_ADC_STREAM_USING_FIFO)
+#include <ipc/completion.h>
+#include <ipc/ringbuffer.h>
+#endif /* defined(RT_ADC_USING_STREAM) && defined(RT_ADC_STREAM_USING_FIFO) */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * @brief ADC control command identifiers.
+ */
+enum rt_adc_control_cmd
+{
+ RT_ADC_CMD_GET_RESOLUTION = RT_DEVICE_CTRL_BASE(ADC) + 1, /**< Get resolution in bits. */
+ RT_ADC_CMD_CALIBRATE, /**< Run ADC calibration. */
+ RT_ADC_CMD_SET_VREF, /**< Set fallback VREF from rt_uint32_t mV. */
+ RT_ADC_CMD_CALC_VREF, /**< Calculate VDDA from a raw VREF sample. */
+ RT_ADC_CMD_GET_VREF_CHANNEL, /**< Get the backend VREF sample channel as rt_uint8_t. */
+ RT_ADC_CMD_SET_SESSION, /**< Configure and cache conversion channels from rt_uint32_t mask. */
+ RT_ADC_CMD_SET_CONFIG, /**< Set backend private device configuration. */
+ RT_ADC_CMD_GET_CONFIG, /**< Get backend private device configuration. */
+ RT_ADC_CMD_CLEAR_CHANNEL_CONFIG /**< Clear backend channel/rank configuration explicitly. */
+};
+
+/**
+ * @brief ADC input mode request flags.
+ */
+enum rt_adc_input_mode
+{
+ RT_ADC_INPUT_MODE_SINGLE_ENDED = 0x01U, /**< Single-ended ADC input mode. */
+ RT_ADC_INPUT_MODE_DIFFERENTIAL = 0x02U /**< Differential ADC input mode. */
+};
+
+/**
+ * @brief Build an ADC channel mask from one logical channel index.
+ * @param _channel Logical ADC channel index.
+ */
+#define RT_ADC_CHANNEL_MASK(_channel) (1UL << (rt_uint32_t)(_channel))
+
+/**
+ * @brief ADC framework runtime state.
+ */
+enum rt_adc_state
+{
+ RT_ADC_STATE_IDLE = 0, /**< The ADC device is idle. */
+ RT_ADC_STATE_LOCKED, /**< The ADC device is temporarily locked by a framework operation. */
+ RT_ADC_STATE_STREAM /**< The ADC device is owned by an active stream session. */
+};
+
+struct rt_adc_device;
+typedef struct rt_adc_device *rt_adc_device_t;
+
+struct rt_adc_sequence_cfg;
+struct rt_adc_stream_cfg;
+
+/**
+ * @brief ADC core driver operation table.
+ */
+struct rt_adc_core_ops
+{
+ /**
+ * @brief Open and initialize the ADC hardware.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ */
+ rt_err_t (*open)(struct rt_adc_device *device);
+
+ /**
+ * @brief Close and deinitialize the ADC hardware.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ */
+ rt_err_t (*close)(struct rt_adc_device *device);
+
+
+ /**
+ * @brief Configure one ADC conversion session before data transfer.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @return Operation status.
+ */
+ rt_err_t (*session_config)(struct rt_adc_device *device, rt_uint32_t channels);
+
+#if defined(RT_ADC_USING_TRIGGER)
+ /**
+ * @brief Preconfigure the ADC backend trigger state for the next hardware configuration pass.
+ * @param device Pointer to the ADC device object.
+ * @param cfg Pointer to the active ADC trigger configuration object.
+ * @return Operation status.
+ * @note The implementation may only validate @p cfg, update pending driver
+ * state, or fill HAL initialization fields. It must not start the
+ * trigger source or arm ADC conversions.
+ */
+ rt_err_t (*trigger_prepare)(struct rt_adc_device *device, const struct rt_adc_trigger_cfg *cfg);
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+ /**
+ * @brief Handle ADC control commands.
+ * @param device Pointer to the ADC device object.
+ * @param cmd Control command.
+ * @param args Pointer to the control argument buffer.
+ * @return Operation status.
+ */
+ rt_err_t (*control)(struct rt_adc_device *device, int cmd, void *args);
+};
+
+/**
+ * @brief ADC finite-sequence operation table.
+ */
+struct rt_adc_sequence_ops
+{
+ /**
+ * @brief Start one active finite transfer session.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the sequence request object.
+ * @return Operation status.
+ */
+ rt_err_t (*start)(struct rt_adc_device *device, rt_uint32_t channels, const struct rt_adc_sequence_cfg *cfg);
+
+ /**
+ * @brief Read one converted sample from an active finite transfer session.
+ * @param device Pointer to the ADC device object.
+ * @param value Pointer to the output sample value.
+ * @param timeout_ms Read timeout in milliseconds; negative means wait forever.
+ * @return Operation status.
+ */
+ rt_err_t (*read)(struct rt_adc_device *device, rt_uint32_t *value, rt_int32_t timeout_ms);
+
+ /**
+ * @brief Stop one active finite transfer session.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ */
+ rt_err_t (*stop)(struct rt_adc_device *device);
+};
+
+#ifdef RT_ADC_USING_STREAM
+/**
+ * @brief ADC stream operation table.
+ */
+struct rt_adc_stream_ops
+{
+ /**
+ * @brief Start one active stream session.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the stream configuration object.
+ * @return Operation status.
+ */
+ rt_err_t (*start)(struct rt_adc_device *device, rt_uint32_t channels, const struct rt_adc_stream_cfg *cfg);
+
+ /**
+ * @brief Synchronize a backend DMA sample range before CPU access.
+ * @param device Pointer to the ADC device object.
+ * @param sample_buffer Pointer to the DMA sample buffer.
+ * @param sample_count Number of samples to synchronize.
+ * @return Operation status.
+ */
+ rt_err_t (*sync)(struct rt_adc_device *device, const rt_uint32_t *sample_buffer, rt_size_t sample_count);
+
+ /**
+ * @brief Stop one active stream session.
+ * @param device Pointer to the ADC device object.
+ * @param hardware_stopped Pointer to the hardware-stop state output.
+ * @return Operation status.
+ *
+ * The backend sets @p hardware_stopped to RT_TRUE after the ADC hardware
+ * conversion path has stopped. When this callback returns an error with
+ * @p hardware_stopped set to RT_TRUE, the framework treats it as a
+ * post-stop cleanup failure and moves the stream to IDLE.
+ */
+ rt_err_t (*stop)(struct rt_adc_device *device, rt_bool_t *hardware_stopped);
+};
+#endif /* RT_ADC_USING_STREAM */
+
+
+/**
+ * @brief ADC driver operation table.
+ */
+struct rt_adc_ops
+{
+ const struct rt_adc_core_ops *core; /**< Core operation table. */
+ const struct rt_adc_sequence_ops *sequence; /**< Sequence-session operations. */
+#ifdef RT_ADC_USING_STREAM
+ const struct rt_adc_stream_ops *stream; /**< Stream-session operations. */
+#endif /* RT_ADC_USING_STREAM */
+};
+
+/**
+ * @brief ADC synchronous one-frame sequence request.
+ */
+struct rt_adc_sequence_cfg
+{
+ rt_uint32_t *buffer; /**< Destination sample buffer. */
+ rt_size_t buffer_length; /**< Destination buffer length in samples. */
+ rt_int32_t timeout_ms; /**< Per-sample timeout; negative means wait forever. */
+};
+
+#ifdef RT_ADC_USING_STREAM
+/**
+ * @brief ADC stream backend event.
+ */
+enum rt_adc_stream_event
+{
+ RT_ADC_STREAM_EVENT_DMA_HALF = 0x01U, /**< ADC stream DMA half-transfer event. */
+ RT_ADC_STREAM_EVENT_DMA_DONE = 0x02U, /**< ADC stream DMA transfer-complete event. */
+ RT_ADC_STREAM_EVENT_ERROR = 0x03U /**< ADC stream backend error event. */
+};
+
+/**
+ * @brief ADC stream buffering policy.
+ */
+enum rt_adc_stream_policy
+{
+#ifdef RT_ADC_STREAM_USING_LATEST
+ /**
+ * @brief Keep only the latest ADC rank values.
+ *
+ * This policy is intended for slow-changing ADC values such as battery
+ * voltage, temperature, potentiometer input, resistor-divider voltage, or
+ * board status monitoring. Old values may be overwritten.
+ *
+ * In this policy, @ref rt_adc_stream_cfg::dma_buffer_length must be exactly
+ * equal to the active ADC frame length, namely the number of enabled ADC
+ * ranks. The backend must expose the DMA buffer as a single latest-frame
+ * layout:
+ *
+ * - dma_buffer[0] stores the latest value of ADC rank 1.
+ * - dma_buffer[1] stores the latest value of ADC rank 2.
+ * - dma_buffer[n] stores the latest value of ADC rank n + 1.
+ *
+ * STM32 regular scan conversion with circular DMA naturally satisfies this
+ * layout when the DMA length equals the ADC rank count. Backends for other
+ * platforms may also support this policy if their DMA engine writes samples
+ * in scan-rank order. If a backend produces packed, interleaved,
+ * descriptor-based, hardware-FIFO, or otherwise non-rank-ordered data, it
+ * must either convert the data into this latest-frame layout internally or
+ * reject the configuration with -RT_ENOSYS.
+ *
+ * @note Latest mode is a low-overhead lockless DMA-buffer view. CPU-side
+ * atomic operations, spin locks, mutexes, or read-write locks cannot
+ * prevent the DMA engine from updating memory concurrently, because
+ * DMA does not participate in CPU locking protocols. This mode only
+ * guarantees that each buffer index maps to the corresponding ADC rank
+ * latest value; it does not guarantee that the copied frame is a
+ * single hardware scan-cycle snapshot. Use RT_ADC_STREAM_POLICY_FIFO
+ * when ordered and frame-accurate samples are required.
+ */
+ RT_ADC_STREAM_POLICY_LATEST = 0,
+#endif /* RT_ADC_STREAM_USING_LATEST */
+#ifdef RT_ADC_STREAM_USING_FIFO
+ /**
+ * @brief Preserve ADC samples in FIFO order.
+ *
+ * This policy is intended for continuous and loss-sensitive sampling such
+ * as waveform, current, vibration, audio-like, motor-control, or
+ * control-loop data where each sample matters.
+ *
+ * The backend reports completed DMA blocks to the ADC framework. The
+ * framework copies those blocks into a ringbuffer and wakes blocking readers
+ * through a completion object.
+ */
+ RT_ADC_STREAM_POLICY_FIFO,
+#endif /* RT_ADC_STREAM_USING_FIFO */
+};
+
+/**
+ * @brief ADC stream DMA data event mode.
+ */
+enum rt_adc_stream_dma_event_mode
+{
+ RT_ADC_STREAM_DMA_EVENT_AUTO = 0, /**< Use the default event mode for the selected stream policy. */
+ RT_ADC_STREAM_DMA_EVENT_NONE, /**< Disable DMA half/full data events; DMA error events remain enabled. */
+ RT_ADC_STREAM_DMA_EVENT_FULL_ONLY, /**< Report only DMA transfer-complete data events to reduce interrupt rate. */
+ RT_ADC_STREAM_DMA_EVENT_HALF_FULL /**< Report both DMA half-transfer and transfer-complete data events. */
+};
+
+/**
+ * @brief ADC stream FIFO data callback type.
+ * @param device Pointer to the ADC device object.
+ * @param sample_count Number of newly accepted samples.
+ * @param user_data Pointer to the user context.
+ * @note This callback is invoked from ADC stream ISR context. It must not
+ * block, sleep, or call APIs that may suspend the current context.
+ */
+typedef void (*rt_adc_stream_callback_t)(struct rt_adc_device *device, rt_size_t sample_count, void *user_data);
+
+/**
+ * @brief ADC stream configuration.
+ * @note The DMA buffer belongs to the caller and must remain valid until
+ * rt_adc_stream_stop() returns. The stream configuration object only
+ * needs to remain valid for the duration of rt_adc_stream_start(). A
+ * backend may use an internal cache-aligned DMA buffer when the caller
+ * buffer is not safe for platform cache maintenance. Backend drivers must
+ * copy scalar configuration fields they need after rt_adc_stream_start()
+ * returns.
+ */
+struct rt_adc_stream_cfg
+{
+ enum rt_adc_stream_policy policy; /**< Stream buffering policy. */
+ enum rt_adc_stream_dma_event_mode dma_event_mode; /**< DMA half/full data event mode. */
+
+ /**
+ * @brief DMA circular staging buffer.
+ *
+ * For RT_ADC_STREAM_POLICY_LATEST, this buffer stores exactly one latest
+ * ADC scan frame. dma_buffer_length must equal the active frame length.
+ * Each element maps to one configured ADC rank.
+ *
+ * For RT_ADC_STREAM_POLICY_FIFO, this buffer is a DMA staging buffer. The
+ * default rt_adc_stream_start() entry validates only basic buffer presence
+ * and capacity. It does not require each DMA data-event block to end on an
+ * ADC scan-frame boundary.
+ *
+ * Use rt_adc_stream_start_frame_aligned_fifo() when continuous FIFO sampling
+ * requires a strict frame-aligned DMA/FIFO layout before the stream starts.
+ */
+ rt_uint32_t *dma_buffer;
+
+ /**
+ * @brief DMA buffer length in samples.
+ */
+ rt_size_t dma_buffer_length;
+#ifdef RT_ADC_STREAM_USING_FIFO
+ rt_uint32_t *fifo_buffer; /**< FIFO storage for FIFO policy. */
+ rt_size_t fifo_buffer_length; /**< FIFO buffer length in samples. */
+ rt_size_t watermark; /**< FIFO wakeup or callback threshold in samples. */
+ rt_adc_stream_callback_t callback; /**< Optional ISR-context FIFO data callback. */
+ void *user_data; /**< User data passed to the FIFO data callback. */
+#endif /* RT_ADC_STREAM_USING_FIFO */
+};
+
+/**
+ * @brief ADC stream runtime control block owned by the ADC framework.
+ */
+struct rt_adc_stream_ctrl
+{
+ rt_bool_t active; /**< Whether a stream backend session is active. */
+ enum rt_adc_stream_policy policy; /**< Active stream buffering policy. */
+ enum rt_adc_stream_dma_event_mode dma_event_mode; /**< Active DMA data event mode. */
+ rt_uint32_t *dma_buffer; /**< DMA circular staging buffer. */
+ rt_size_t dma_buffer_length; /**< DMA buffer length in samples. */
+ rt_size_t frame_length; /**< Samples in one ADC scan frame. */
+#ifdef RT_ADC_STREAM_USING_FIFO
+ struct rt_ringbuffer fifo; /**< FIFO storage for FIFO policy. */
+ rt_bool_t fifo_enabled; /**< FIFO initialized flag. */
+ /**
+ * @brief Reader wake completion for FIFO stream.
+ *
+ * The FIFO ringbuffer is the source of truth for available data. This
+ * completion object is only a wakeup edge for blocking reads.
+ */
+ struct rt_completion rx_cpt;
+ rt_size_t overflow_count; /**< FIFO overrun count. */
+ rt_size_t watermark; /**< FIFO wakeup or callback threshold. */
+ rt_adc_stream_callback_t callback; /**< Optional FIFO data callback. */
+ void *user_data; /**< Callback private data. */
+#endif /* RT_ADC_STREAM_USING_FIFO */
+ rt_atomic_t last_error; /**< Atomic last stream backend error. */
+};
+#endif /* RT_ADC_USING_STREAM */
+
+/**
+ * @brief ADC active session runtime control block.
+ */
+struct rt_adc_session_ctrl
+{
+ rt_uint32_t channels; /**< Cached active ADC channel selection mask. */
+ rt_size_t channel_count; /**< Cached active session channel count. */
+ rt_bool_t configured; /**< Whether the cached session configuration is valid. */
+};
+
+/**
+ * @brief ADC device object.
+ */
+struct rt_adc_device
+{
+ struct rt_device parent; /**< RT-Thread device object. */
+ const struct rt_adc_ops *ops; /**< ADC driver operation table. */
+ rt_atomic_t state; /**< Atomic ADC runtime state. */
+ struct rt_spinlock spinlock; /**< Protects ADC stream FIFO shared by thread context and ISR. */
+ rt_uint32_t default_vref_mv; /**< Default reference voltage in millivolts. */
+ struct rt_adc_session_ctrl session_ctrl; /**< ADC active session control block. */
+#if defined(RT_ADC_USING_TRIGGER)
+ struct rt_adc_trigger_ctrl trigger_ctrl; /**< ADC trigger runtime control block. */
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+#ifdef RT_ADC_USING_STREAM
+ struct rt_adc_stream_ctrl stream_ctrl; /**< ADC stream runtime control block. */
+#endif /* RT_ADC_USING_STREAM */
+};
+
+/**
+ * @brief Count selected ADC channels in one channel mask.
+ * @param channels ADC channel selection mask.
+ * @return Number of selected channels.
+ */
+rt_inline rt_size_t rt_adc_channel_mask_count(rt_uint32_t channels)
+{
+#if defined(__GNUC__) || defined(__clang__)
+ return (rt_size_t)__builtin_popcount((unsigned int)channels);
+#else
+ rt_size_t count;
+
+ count = 0U;
+ while (channels != 0U)
+ {
+ channels &= channels - 1U;
+ count++;
+ }
+
+ return count;
+#endif /* defined(__GNUC__) || defined(__clang__) */
+}
+
+/**
+ * @brief Take the lowest selected ADC channel from one channel mask.
+ * @param channels Pointer to the ADC channel selection mask.
+ * @return Selected channel index, or 0xff if the mask is empty.
+ */
+rt_inline rt_uint8_t rt_adc_channel_mask_take_lsb(rt_uint32_t *channels)
+{
+ rt_uint8_t bit;
+
+ if ((channels == RT_NULL) || (*channels == 0U))
+ {
+ return 0xffU;
+ }
+
+#if defined(__GNUC__) || defined(__clang__)
+ bit = (rt_uint8_t)__builtin_ctz((unsigned int)*channels);
+#else
+ for (bit = 0U; bit < 32U; bit++)
+ {
+ if ((*channels & RT_ADC_CHANNEL_MASK(bit)) != 0U)
+ {
+ break;
+ }
+ }
+#endif /* defined(__GNUC__) || defined(__clang__) */
+
+ *channels &= ~RT_ADC_CHANNEL_MASK(bit);
+ return bit;
+}
+
+/**
+ * @brief Register an ADC device with the RT-Thread device framework.
+ * @param device Pointer to the ADC device object.
+ * @param name Pointer to the ADC device name string.
+ * @param ops Pointer to the ADC driver operation table.
+ * @param user_data Pointer to driver private data.
+ * @return Operation status.
+ */
+rt_err_t rt_hw_adc_register(rt_adc_device_t device, const char *name, const struct rt_adc_ops *ops, const void *user_data);
+
+/**
+ * @brief Get the raw sample index of one channel in an ADC session mask.
+ * @param session_channels Configured ADC session channel mask.
+ * @param channel Single-channel mask to locate.
+ * @param index Pointer to the output raw sample index.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_session_channel_index(rt_uint32_t session_channels, rt_uint32_t channel, rt_size_t *index);
+
+
+/**
+ * @brief Read one ADC conversion sequence frame.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the ADC sequence request object.
+ * @param read_count Pointer to the stored sample count.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_read_sequence(rt_adc_device_t device, rt_uint32_t channels,
+ const struct rt_adc_sequence_cfg *cfg, rt_size_t *read_count);
+
+#ifdef RT_ADC_USING_STREAM
+/**
+ * @brief Start one ADC stream session.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the ADC stream configuration object.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_stream_start(rt_adc_device_t device, rt_uint32_t channels, const struct rt_adc_stream_cfg *cfg);
+
+#ifdef RT_ADC_STREAM_USING_FIFO
+/**
+ * @brief Start one FIFO ADC stream with strict frame-aligned buffer layout.
+ * @param device Pointer to the ADC device object.
+ * @param channels ADC channel selection mask.
+ * @param cfg Pointer to the ADC stream configuration object.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_stream_start_frame_aligned_fifo(rt_adc_device_t device, rt_uint32_t channels, const struct rt_adc_stream_cfg *cfg);
+#endif /* RT_ADC_STREAM_USING_FIFO */
+
+/**
+ * @brief Read converted samples from an active ADC stream session.
+ * @param device Pointer to the ADC device object.
+ * @param buffer Pointer to the destination sample buffer.
+ * @param sample_count Number of samples to read.
+ * @param timeout_ms Read timeout in milliseconds; negative means wait forever.
+ * @return Number of samples read, or a negative RT-Thread error code.
+ */
+rt_ssize_t rt_adc_stream_read(rt_adc_device_t device, rt_uint32_t *buffer, rt_size_t sample_count, rt_int32_t timeout_ms);
+
+/**
+ * @brief Cancel a blocked ADC stream reader before stream shutdown.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_stream_cancel(rt_adc_device_t device);
+
+/**
+ * @brief Stop one ADC stream session.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_stream_stop(rt_adc_device_t device);
+
+/**
+ * @brief Notify the ADC framework of one stream backend event.
+ * @param device Pointer to the ADC device object.
+ * @param event ADC stream event reported by the backend or ISR.
+ * @param sample_buffer Pointer to the completed sample buffer block.
+ * @param sample_count Number of samples in @p sample_buffer.
+ * @return Operation status.
+ */
+rt_err_t rt_hw_adc_stream_isr(rt_adc_device_t device, enum rt_adc_stream_event event,
+ const rt_uint32_t *sample_buffer, rt_size_t sample_count);
+#endif /* RT_ADC_USING_STREAM */
+
+/**
+ * @brief Convert one raw ADC sample to millivolts.
+ * @param raw_value Raw ADC sample value.
+ * @param vref_mv ADC reference voltage in millivolts.
+ * @param resolution_bits ADC resolution in bits.
+ * @param voltage_mv Pointer to the output voltage in millivolts.
+ * @return Operation status.
+ */
+rt_err_t rt_adc_raw_to_voltage_mv(rt_uint32_t raw_value, rt_uint32_t vref_mv, rt_uint8_t resolution_bits, rt_uint32_t *voltage_mv);
+
+/**
+ * @brief Convert one ADC raw sample to a scaled voltage in millivolts.
+ * @param raw_value Raw ADC sample code.
+ * @param vref_mv ADC reference voltage in millivolts.
+ * @param resolution_bits ADC resolution in bits.
+ * @param scale_num Voltage scaling numerator.
+ * @param scale_den Voltage scaling denominator.
+ * @param voltage_mv Pointer to the output scaled voltage in millivolts.
+ * @return Operation status.
+ *
+ * The scaled voltage is calculated as:
+ *
+ * raw_to_voltage_mv(raw_value) * scale_num / scale_den
+ *
+ * @note @p scale_num and @p scale_den may describe any positive rational
+ * scaling ratio that does not overflow the internal calculation. The
+ * final scaled millivolt value must fit in rt_uint32_t.
+ *
+ * @retval RT_EOK The conversion completed successfully.
+ * @retval -RT_EINVAL A pointer is null, a scale argument is zero, the ADC
+ * conversion arguments are invalid, or the scaled result overflows
+ * rt_uint32_t.
+ */
+rt_err_t rt_adc_raw_to_scaled_voltage_mv(rt_uint32_t raw_value, rt_uint32_t vref_mv, rt_uint8_t resolution_bits, rt_uint32_t scale_num, rt_uint32_t scale_den, rt_uint32_t *voltage_mv);
+
+/**
+ * @brief Read ADC samples and convert them to millivolts.
+ * @param device Pointer to the ADC device object.
+ * @param voltages_mv Pointer to the output voltage buffer in millivolts.
+ * @param size Output voltage buffer length.
+ * @param timeout_ms Per-sample timeout in milliseconds; negative means wait forever.
+ * @return Operation status.
+ *
+ * @note This API reads the currently configured ADC session and does not modify
+ * the active channel mask. If default_vref_mv is zero, callers that need
+ * backend-calculated VDDA must include the backend VREF channel in the
+ * configured session. The VREF sample is used only as the reference for
+ * conversion and is not auto-inserted by this API.
+ */
+rt_err_t rt_adc_voltage(rt_adc_device_t device, rt_uint32_t *voltages_mv, rt_size_t size, rt_int32_t timeout_ms);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __ADC_V2_H__ */
diff --git a/components/drivers/include/drivers/adc_v2_trigger.h b/components/drivers/include/drivers/adc_v2_trigger.h
new file mode 100644
index 0000000000..bb328b8dba
--- /dev/null
+++ b/components/drivers/include/drivers/adc_v2_trigger.h
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2006-2026, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-05-20 wdfk-prog add ADC V2 trigger declarations
+ */
+
+/**
+ * @file adc_v2_trigger.h
+ * @brief ADC V2 trigger framework declarations.
+ */
+
+#ifndef __ADC_V2_TRIGGER_H__
+#define __ADC_V2_TRIGGER_H__
+
+#include <rtconfig.h>
+#include <rtthread.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#if defined(RT_ADC_USING_TRIGGER)
+struct rt_adc_device;
+struct rt_device;
+
+/**
+ * @brief ADC hardware trigger event type.
+ *
+ * @note A platform backend may support only part of these trigger types.
+ * Unsupported trigger types shall be rejected by the framework or backend
+ * with an error such as -RT_ENOSYS.
+ */
+enum rt_adc_trigger_type
+{
+ RT_ADC_TRIGGER_TIMER_UPDATE = 0, /**< Use a timer update/TRGO event. */
+ RT_ADC_TRIGGER_TIMER_COMPARE, /**< Use a timer compare event. */
+ RT_ADC_TRIGGER_PWM_EDGE, /**< Use a PWM edge event. */
+ RT_ADC_TRIGGER_EXTI_EDGE, /**< Use an external interrupt edge event. */
+ RT_ADC_TRIGGER_ANALOG_COMPARE, /**< Use an analog comparator event. */
+ RT_ADC_TRIGGER_BACKEND, /**< Use a backend-specific trigger event. */
+};
+
+/**
+ * @brief ADC trigger edge selector.
+ */
+enum rt_adc_trigger_edge
+{
+ RT_ADC_TRIGGER_EDGE_NONE = 0, /**< No edge selection is required. */
+ RT_ADC_TRIGGER_EDGE_RISING, /**< Use the rising edge. */
+ RT_ADC_TRIGGER_EDGE_FALLING, /**< Use the falling edge. */
+ RT_ADC_TRIGGER_EDGE_BOTH, /**< Use both rising and falling edges. */
+};
+
+/**
+ * @brief Timer-based ADC trigger event.
+ */
+struct rt_adc_trigger_timer_event
+{
+ struct rt_device *timer; /**< Timer device used as trigger source. */
+ rt_uint32_t freq_hz; /**< Timer trigger output frequency in hertz. */
+ rt_uint16_t channel; /**< Timer channel, or 0 for update/TRGO event. */
+ rt_uint16_t reserved; /**< Reserved for future extension. */
+};
+
+/**
+ * @brief PWM-based ADC trigger event.
+ */
+struct rt_adc_trigger_pwm_event
+{
+ struct rt_device *pwm; /**< PWM device used as trigger source. */
+ rt_uint16_t channel; /**< PWM channel. */
+ enum rt_adc_trigger_edge edge; /**< PWM edge used as trigger event. */
+};
+
+/**
+ * @brief External interrupt ADC trigger event.
+ */
+struct rt_adc_trigger_exti_event
+{
+ struct rt_device *pin; /**< Pin or GPIO controller device. */
+ rt_uint16_t line; /**< External interrupt line or pin number. */
+ enum rt_adc_trigger_edge edge; /**< Interrupt edge used as trigger event. */
+};
+
+/**
+ * @brief Analog comparator ADC trigger event.
+ */
+struct rt_adc_trigger_compare_event
+{
+ struct rt_device *comparator; /**< Optional comparator device used as trigger source. */
+ rt_uint16_t channel; /**< Comparator channel or instance index. */
+ enum rt_adc_trigger_edge edge; /**< Comparator output edge used as trigger event. */
+};
+
+/**
+ * @brief Backend-specific ADC trigger event.
+ */
+struct rt_adc_trigger_backend_event
+{
+ rt_uint32_t selector; /**< Backend-specific trigger selector. */
+ rt_uint32_t edge; /**< Backend-specific edge selector. */
+ const void *data; /**< Optional backend-specific configuration. */
+ rt_size_t data_size; /**< Size of backend-specific configuration. */
+};
+
+/**
+ * @brief ADC trigger configuration.
+ */
+struct rt_adc_trigger_cfg
+{
+ enum rt_adc_trigger_type type; /**< Trigger event type. */
+ rt_uint32_t flags; /**< Common trigger flags, reserved for future extension. */
+
+ union
+ {
+ struct rt_adc_trigger_timer_event timer; /**< Timer trigger event. */
+ struct rt_adc_trigger_pwm_event pwm; /**< PWM trigger event. */
+ struct rt_adc_trigger_exti_event exti; /**< External interrupt trigger event. */
+ struct rt_adc_trigger_compare_event compare; /**< Comparator trigger event. */
+ struct rt_adc_trigger_backend_event backend; /**< Backend-specific trigger event. */
+ } event; /**< Trigger event payload. */
+};
+
+/**
+ * @brief ADC trigger runtime control block.
+ */
+struct rt_adc_trigger_ctrl
+{
+ struct rt_adc_trigger_cfg cfg; /**< Cached device-level hardware trigger configuration. */
+ rt_bool_t configured; /**< Whether cfg contains a cached hardware trigger request. */
+};
+
+/**
+ * @brief Set one device-level ADC hardware trigger request.
+ * @param device Pointer to the ADC device object.
+ * @param cfg Pointer to the ADC trigger configuration object.
+ * @return Operation status.
+ * @note This function only checks the framework-level trigger structure and
+ * caches the request. It does not write ADC trigger registers immediately.
+ * Backend compatibility is reported when the cached trigger is
+ * preconfigured before a conversion hardware configuration.
+ */
+rt_err_t rt_adc_trigger_set(struct rt_adc_device *device, const struct rt_adc_trigger_cfg *cfg);
+
+/**
+ * @brief Clear the cached device-level ADC hardware trigger request.
+ * @param device Pointer to the ADC device object.
+ * @return Operation status.
+ * @note This function does not write ADC trigger registers immediately. The
+ * next conversion hardware configuration preconfigures the backend
+ * software/default trigger state.
+ */
+rt_err_t rt_adc_trigger_clear(struct rt_adc_device *device);
+
+/**
+ * @brief Check whether one ADC device has a cached hardware trigger request.
+ * @param device Pointer to the ADC device object.
+ * @return RT_TRUE if a hardware trigger request is set, otherwise RT_FALSE.
+ */
+rt_bool_t rt_adc_trigger_is_set(struct rt_adc_device *device);
+#endif /* defined(RT_ADC_USING_TRIGGER) */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __ADC_V2_TRIGGER_H__ */
diff --git a/components/drivers/include/drivers/clock_time.h b/components/drivers/include/drivers/clock_time.h
index 7374b97d00..c43c079eb0 100644
--- a/components/drivers/include/drivers/clock_time.h
+++ b/components/drivers/include/drivers/clock_time.h
@@ -47,9 +47,41 @@ typedef enum
CLOCK_TIMER_CTRL_FREQ_SET = RT_DEVICE_CTRL_BASE(Timer) + 0x01,
CLOCK_TIMER_CTRL_STOP = RT_DEVICE_CTRL_BASE(Timer) + 0x02,
CLOCK_TIMER_CTRL_INFO_GET = RT_DEVICE_CTRL_BASE(Timer) + 0x03,
- CLOCK_TIMER_CTRL_MODE_SET = RT_DEVICE_CTRL_BASE(Timer) + 0x04
+ CLOCK_TIMER_CTRL_MODE_SET = RT_DEVICE_CTRL_BASE(Timer) + 0x04,
+#if defined(RT_USING_CLOCK_TIMER_TRIGGER)
+ CLOCK_TIMER_CTRL_TRIGGER_CONFIG = RT_DEVICE_CTRL_BASE(Timer) + 0x05,
+ CLOCK_TIMER_CTRL_TRIGGER_START = RT_DEVICE_CTRL_BASE(Timer) + 0x06,
+ CLOCK_TIMER_CTRL_TRIGGER_STOP = RT_DEVICE_CTRL_BASE(Timer) + 0x07,
+ CLOCK_TIMER_CTRL_TRIGGER_RELEASE = RT_DEVICE_CTRL_BASE(Timer) + 0x08
+#endif /* defined(RT_USING_CLOCK_TIMER_TRIGGER) */
} rt_clock_timer_ctrl_t;
+#if defined(RT_USING_CLOCK_TIMER_TRIGGER)
+/**
+ * @brief Hardware timer trigger output event type.
+ */
+enum rt_clock_timer_trigger_event
+{
+ CLOCK_TIMER_TRIGGER_EVENT_UPDATE = 0, /**< Generate trigger on timer update/overflow event. */
+ CLOCK_TIMER_TRIGGER_EVENT_COMPARE, /**< Generate trigger on timer compare match event. */
+ CLOCK_TIMER_TRIGGER_EVENT_PWM_EDGE, /**< Generate trigger on timer PWM output reference edge. */
+ CLOCK_TIMER_TRIGGER_EVENT_BACKEND /**< Generate trigger from a backend-specific timer event. */
+};
+
+/**
+ * @brief Hardware trigger output configuration.
+ * @note This interface does not arbitrate timer ownership. Callers must
+ * provide a timer dedicated to hardware trigger output.
+ */
+struct rt_clock_timer_trigger_cfg
+{
+ rt_uint32_t freq_hz; /**< Trigger output frequency in hertz. */
+ enum rt_clock_timer_trigger_event event; /**< Timer event used as trigger output. */
+ rt_uint16_t channel; /**< Timer compare channel, or 0 for update event. */
+ rt_uint16_t reserved; /**< Reserved for future extension. */
+};
+#endif /* defined(RT_USING_CLOCK_TIMER_TRIGGER) */
+
typedef enum
{
CLOCK_TIMER_MODE_ONESHOT = 0x01,
diff --git a/components/drivers/include/rtdevice.h b/components/drivers/include/rtdevice.h
index e6eb43bc57..a9da14d322 100644
--- a/components/drivers/include/rtdevice.h
+++ b/components/drivers/include/rtdevice.h
@@ -272,9 +272,11 @@ extern "C" {
#include "drivers/dev_audio.h"
#endif /* RT_USING_AUDIO */
-#ifdef RT_USING_ADC
+#ifdef RT_USING_ADC_V2
+#include "drivers/adc_v2.h"
+#elif defined(RT_USING_ADC)
#include "drivers/adc.h"
-#endif /* RT_USING_ADC */
+#endif /* RT_USING_ADC_V2 */
#ifdef RT_USING_DAC
#include "drivers/dac.h"
diff --git a/components/drivers/misc/Kconfig b/components/drivers/misc/Kconfig
index 0a7788bce5..71f42ee67b 100644
--- a/components/drivers/misc/Kconfig
+++ b/components/drivers/misc/Kconfig
@@ -1,11 +1,3 @@
-menuconfig RT_USING_ADC
- bool "Using ADC device drivers"
- default n
-
-if RT_USING_DM && RT_USING_ADC
- osource "$(SOC_DM_ADC_DIR)/Kconfig"
-endif
-
config RT_USING_DAC
bool "Using DAC device drivers"
default n
diff --git a/components/drivers/misc/SConscript b/components/drivers/misc/SConscript
index 5b74ac93e5..23727a5ce9 100644
--- a/components/drivers/misc/SConscript
+++ b/components/drivers/misc/SConscript
@@ -5,9 +5,6 @@ src = []
CPPPATH = [cwd + '/../include']
group = []
-if GetDepend(['RT_USING_ADC']):
- src = src + ['adc.c']
-
if GetDepend(['RT_USING_DAC']):
src = src + ['dac.c']