diff options
Diffstat (limited to 'components/drivers')
| -rwxr-xr-x | components/drivers/Kconfig | 1 | ||||
| -rw-r--r-- | components/drivers/adc/Kconfig | 121 | ||||
| -rw-r--r-- | components/drivers/adc/SConscript | 23 | ||||
| -rw-r--r-- | components/drivers/adc/adc.c (renamed from components/drivers/misc/adc.c) | 0 | ||||
| -rw-r--r-- | components/drivers/adc/adc_v2.c | 1779 | ||||
| -rw-r--r-- | components/drivers/adc/adc_v2_internal.h | 62 | ||||
| -rw-r--r-- | components/drivers/adc/adc_v2_msh.c | 1225 | ||||
| -rw-r--r-- | components/drivers/adc/adc_v2_trigger.c | 411 | ||||
| -rw-r--r-- | components/drivers/clock_time/Kconfig | 11 | ||||
| -rw-r--r-- | components/drivers/include/drivers/adc_v2.h | 622 | ||||
| -rw-r--r-- | components/drivers/include/drivers/adc_v2_trigger.h | 171 | ||||
| -rw-r--r-- | components/drivers/include/drivers/clock_time.h | 34 | ||||
| -rw-r--r-- | components/drivers/include/rtdevice.h | 6 | ||||
| -rw-r--r-- | components/drivers/misc/Kconfig | 8 | ||||
| -rw-r--r-- | components/drivers/misc/SConscript | 3 |
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'] |
