From 3e916c0a714423542b80262da26c3ccc54e167fc Mon Sep 17 00:00:00 2001 From: Vladislav Pejic Date: Wed, 21 May 2025 12:46:19 +0200 Subject: [PATCH 1/6] driver: adc: Added stream APIs for ADC Introduce a streaming APIs for ADC devices. Two new APIs are added to the adc_driver_api: submit and get_decoder. Added decoder following APIs: get_frame_count, get_size_info, decode, has_trigger. Supported triggers are: - ADC_TRIG_DATA_READY - ADC_TRIG_FIFO_WATERMARK - ADC_TRIG_FIFO_FULL Supported operations to be done on trigger: - include - whatever data is associated with the trigger - nop - do nothing with data associated with the trigger - drop - clear data associated with the trigger Some changes to the linker scripts were needed to add decoder APIs. Signed-off-by: Vladislav Pejic --- cmake/linker_script/common/common-rom.cmake | 4 + drivers/adc/CMakeLists.txt | 1 + drivers/adc/Kconfig | 9 + drivers/adc/default_rtio_adc.c | 330 ++++++++++++++++++ include/zephyr/drivers/adc.h | 325 +++++++++++++++++ .../linker/common-rom/common-rom-misc.ld | 4 + scripts/build/gen_kobject_list.py | 3 +- 7 files changed, 675 insertions(+), 1 deletion(-) create mode 100644 drivers/adc/default_rtio_adc.c diff --git a/cmake/linker_script/common/common-rom.cmake b/cmake/linker_script/common/common-rom.cmake index a6d3e09bf2644..91795e82fed1e 100644 --- a/cmake/linker_script/common/common-rom.cmake +++ b/cmake/linker_script/common/common-rom.cmake @@ -153,6 +153,10 @@ if(CONFIG_SENSOR_ASYNC_API) zephyr_iterable_section(NAME sensor_decoder_api KVMA RAM_REGION GROUP RODATA_REGION) endif() +if(CONFIG_ADC_STREAM) + zephyr_iterable_section(NAME adc_decoder_api KVMA RAM_REGION GROUP RODATA_REGION SUBALIGN ${CONFIG_LINKER_ITERABLE_SUBALIGN}) +endif() + if(CONFIG_MCUMGR) zephyr_iterable_section(NAME mcumgr_handler KVMA RAM_REGION GROUP RODATA_REGION) endif() diff --git a/drivers/adc/CMakeLists.txt b/drivers/adc/CMakeLists.txt index 571318ac56d39..b6d5cd7f77849 100644 --- a/drivers/adc/CMakeLists.txt +++ b/drivers/adc/CMakeLists.txt @@ -65,6 +65,7 @@ zephyr_library_sources_ifdef(CONFIG_ADC_MAX32 adc_max32.c) zephyr_library_sources_ifdef(CONFIG_ADC_AD4114 adc_ad4114.c) zephyr_library_sources_ifdef(CONFIG_ADC_AD7124 adc_ad7124.c) zephyr_library_sources_ifdef(CONFIG_ADC_AD405X adc_ad405x.c) +zephyr_library_sources_ifdef(CONFIG_ADC_STREAM default_rtio_adc.c) zephyr_library_sources_ifdef(CONFIG_ADC_AD4130 adc_ad4130.c) zephyr_library_sources_ifdef(CONFIG_ADC_REALTEK_RTS5912 adc_realtek_rts5912.c) zephyr_library_sources_ifdef(CONFIG_ADC_TI_AM335X adc_ti_am335x.c) diff --git a/drivers/adc/Kconfig b/drivers/adc/Kconfig index 7f5263af515f4..9145db22fa7af 100644 --- a/drivers/adc/Kconfig +++ b/drivers/adc/Kconfig @@ -50,6 +50,15 @@ config ADC_INIT_PRIORITY help ADC driver device initialization priority. +config ADC_STREAM + bool "ADC stream support" + select RTIO + select RTIO_SYS_MEM_BLOCKS + select RTIO_CONSUME_SEM + select RTIO_WORKQ + help + This option enables the stream API calls. + module = ADC module-str = ADC source "subsys/logging/Kconfig.template.log_config" diff --git a/drivers/adc/default_rtio_adc.c b/drivers/adc/default_rtio_adc.c new file mode 100644 index 0000000000000..63e735b081234 --- /dev/null +++ b/drivers/adc/default_rtio_adc.c @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2023 Google LLC. + * Copyright (c) 2024 Croxel Inc. + * Copyright (c) 2025 Analog Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +LOG_MODULE_REGISTER(adc_compat, CONFIG_ADC_LOG_LEVEL); + +static void adc_submit_fallback(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe); + +static void adc_iodev_submit(struct rtio_iodev_sqe *iodev_sqe) +{ + const struct adc_read_config *cfg = iodev_sqe->sqe.iodev->data; + const struct device *dev = cfg->adc; + const struct adc_driver_api *api = dev->api; + + if (api->submit != NULL) { + api->submit(dev, iodev_sqe); + } else if (!cfg->is_streaming) { + adc_submit_fallback(dev, iodev_sqe); + } else { + rtio_iodev_sqe_err(iodev_sqe, -ENOTSUP); + } +} + +const struct rtio_iodev_api __adc_iodev_api = { + .submit = adc_iodev_submit, +}; + +/** + * @brief Compute the required header size + * + * This function takes into account alignment of the q31 values that will follow the header. + * + * @param[in] num_output_samples The number of samples to represent + * @return The number of bytes needed for this sample frame's header + */ +static inline uint32_t compute_read_buf_size(const struct adc_dt_spec *adc_spec, int num_channels) +{ + uint32_t size = 0; + + for (int i = 0; i < num_channels; ++i) { + size += adc_spec[i].resolution / 8; + if (adc_spec[i].resolution % 8) { + size++; + } + } + + /* Align to 4 bytes */ + if (size % 4) { + size += 4 - (size % 4); + } + + return size; +} + +/** + * @brief Compute the required header size + * + * This function takes into account alignment of the q31 values that will follow the header. + * + * @param[in] num_output_samples The number of samples to represent + * @return The number of bytes needed for this sample frame's header + */ +static inline uint32_t compute_header_size(int num_output_samples) +{ + uint32_t size = sizeof(struct adc_data_generic_header) + + (num_output_samples * sizeof(struct adc_chan_spec)); + return (size + 3) & ~0x3; +} + +/** + * @brief Compute the minimum number of bytes needed + * + * @param[in] num_output_samples The number of samples to represent + * @return The number of bytes needed for this sample frame + */ +static inline uint32_t compute_min_buf_len(int num_output_samples) +{ + return compute_header_size(num_output_samples) + (num_output_samples * sizeof(q31_t)); +} + +/** + * @brief Convert sample to q31_t format + * + * @param[in] out Pointer to the output q31_t value + * @param[in] data_in The input data to convert + * @param[in] channel The ADC channel specification + * @param[in] adc_shift The shift value for the ADC + */ +static inline void adc_convert_q31(q31_t *out, uint64_t data_in, + const struct adc_dt_spec *adc_spec, uint8_t adc_shift) +{ + uint32_t scale = BIT(adc_spec->resolution); + uint8_t data_size = adc_spec->resolution / 8; + + if (adc_spec->resolution % 8) { + data_size++; + } + + /* In Differential mode, 1 bit is used for sign */ + if (adc_spec->channel_cfg.differential) { + scale = BIT(adc_spec->resolution - 1); + } + + uint32_t sensitivity = (adc_spec->vref_mv * (scale - 1)) / scale + * 1000 / scale; /* uV / LSB */ + + *out = BIT(31 - adc_shift)/* scaling to q_31*/ * sensitivity / 1000000/*uV to V*/ * data_in; +} + +/** + * @brief Compute the number of bits needed to represent the vref_mv + * + * @param[in] vref_mv The reference voltage in mV + * @return The number of bits needed to represent the vref_mv + */ +uint8_t adc_convert_vref_to_shift(uint16_t vref_mv) +{ + uint8_t count = 1; + + while (1) { + vref_mv /= 2; + if (vref_mv) { + count++; + } else { + break; + } + } + return count; +} + +/** + * @brief Fallback function for retrofiting old drivers to rtio (sync) + * + * @param[in] iodev_sqe The read submission queue event + */ +static void adc_submit_fallback_sync(struct rtio_iodev_sqe *iodev_sqe) +{ + const struct adc_read_config *cfg = iodev_sqe->sqe.iodev->data; + const struct device *dev = cfg->adc; + const struct adc_dt_spec *adc_spec = cfg->adc_spec; + const int num_output_samples = cfg->adc_spec_cnt; + uint32_t min_buf_len = compute_min_buf_len(num_output_samples); + uint64_t timestamp_ns = k_ticks_to_ns_floor64(k_uptime_ticks()); + uint8_t read_buf_size = compute_read_buf_size(adc_spec, num_output_samples); + uint8_t sample_buffer[read_buf_size]; + struct adc_sequence sequence = { + .buffer = sample_buffer, + .buffer_size = read_buf_size, + }; + int rc = adc_read(dev, &sequence); + + uint8_t *buf; + uint32_t buf_len; + + /* Check that the fetch succeeded */ + if (rc != 0) { + LOG_WRN("Failed to fetch samples"); + rtio_iodev_sqe_err(iodev_sqe, rc); + return; + } + + /* Get the buffer for the frame, it may be allocated dynamically by the rtio context */ + rc = rtio_sqe_rx_buf(iodev_sqe, min_buf_len, min_buf_len, &buf, &buf_len); + if (rc != 0) { + LOG_WRN("Failed to get a read buffer of size %u bytes", min_buf_len); + rtio_iodev_sqe_err(iodev_sqe, rc); + return; + } + + /* Set the timestamp and num_channels */ + struct adc_data_generic_header *header = (struct adc_data_generic_header *)buf; + + header->timestamp_ns = timestamp_ns; + header->num_channels = num_output_samples; + header->shift = 0; + + q31_t *q = (q31_t *)(buf + compute_header_size(num_output_samples)); + uint8_t *sample_pointer = sample_buffer; + + /* Populate values, update shift, and set channels */ + for (size_t i = 0; i < num_output_samples; ++i) { + uint8_t sample_size = adc_spec[i].resolution / 8; + + if (adc_spec[i].resolution % 8) { + sample_size++; + } + + uint64_t sample = 0; + + memcpy(&sample, sample_pointer, sample_size); + sample_pointer += sample_size; + if ((adc_spec[i].channel_cfg.differential) && + (sample & (BIT(adc_spec[i].resolution - 1)))) { + sample |= ~BIT_MASK(adc_spec[i].resolution); + } + + header->channels[i].chan_idx = adc_spec[i].channel_id; + header->channels[i].chan_resolution = adc_spec[i].resolution; + + int8_t new_shift = adc_convert_vref_to_shift(adc_spec[i].vref_mv); + + if (header->shift < new_shift) { + /* + * Shift was updated, need to convert all the existing q values. This could + * be optimized by calling zdsp_scale_q31() but that would force a + * dependency between sensors and the zDSP subsystem. + */ + for (int q_idx = 0; q_idx < i; ++q_idx) { + q[q_idx] = q[q_idx] >> (new_shift - header->shift); + } + header->shift = new_shift; + } + + adc_convert_q31(&q[i], sample, &adc_spec[i], header->shift); + } + LOG_DBG("Total channels in header: %" PRIu32, header->num_channels); + rtio_iodev_sqe_ok(iodev_sqe, 0); +} + +/** + * @brief Fallback function for retrofiting old drivers to rtio + * + * @param[in] dev The ADC device to read + * @param[in] iodev_sqe The read submission queue event + */ +static void adc_submit_fallback(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe) +{ + struct rtio_work_req *req = rtio_work_req_alloc(); + + if (req == NULL) { + LOG_ERR("RTIO work item allocation failed. Consider to increase " + "CONFIG_RTIO_WORKQ_POOL_ITEMS."); + rtio_iodev_sqe_err(iodev_sqe, -ENOMEM); + return; + } + + rtio_work_req_submit(req, iodev_sqe, adc_submit_fallback_sync); +} + +/** + * @brief Default decoder get frame count + * + * Default reader can only ever service a single frame at a time. + * + * @param[in] buffer The data buffer to parse + * @param[in] channel The channel to get the count for + * @param[out] frame_count The number of frames in the buffer (always 1) + * @return 0 in all cases + */ +static int get_frame_count(const uint8_t *buffer, uint32_t channel, uint16_t *frame_count) +{ + *frame_count = 1; + return 0; +} + +int adc_natively_supported_channel_size_info(struct adc_dt_spec adc_spec, uint32_t channel, + size_t *base_size, size_t *frame_size) +{ + __ASSERT_NO_MSG(base_size != NULL); + __ASSERT_NO_MSG(frame_size != NULL); + + *base_size = sizeof(struct adc_data); + *frame_size = sizeof(struct adc_sample_data); + return 0; +} + +static int get_q31_value(const struct adc_data_generic_header *header, const q31_t *values, + uint32_t channel, q31_t *out) +{ + for (size_t i = 0; i < header->num_channels; ++i) { + if (channel == header->channels[i].chan_idx) { + *out = values[i]; + return 0; + } + } + + return -EINVAL; +} + +/** + * @brief Decode up to N samples from the buffer + * + * This function will never wrap frames. If 1 channel is available in the current frame and + * @p max_count is 2, only 1 channel will be decoded and the frame iterator will be modified + * so that the next call to decode will begin at the next frame. + * + * @param[in] buffer The buffer provided on the :c:struct:`rtio` context + * @param[in] channel The channel to decode + * @param[in,out] fit The current frame iterator + * @param[in] max_count The maximum number of channels to decode. + * @param[out] data_out The decoded data + * @return 0 no more samples to decode + * @return >0 the number of decoded frames + * @return <0 on error + */ +static int decode(const uint8_t *buffer, uint32_t channel, uint32_t *fit, + uint16_t max_count, void *data_out) +{ + const struct adc_data_generic_header *header = + (const struct adc_data_generic_header *)buffer; + const q31_t *q = (const q31_t *)(buffer + compute_header_size(header->num_channels)); + struct adc_data *data_out_q31 = (struct adc_data *)data_out; + + if (*fit != 0 || max_count < 1) { + return -EINVAL; + } + + data_out_q31->header.base_timestamp_ns = header->timestamp_ns; + data_out_q31->header.reading_count = 1; + data_out_q31->shift = header->shift; + data_out_q31->readings[0].timestamp_delta = 0; + + *fit = 1; + + return get_q31_value(header, q, channel, &data_out_q31->readings[0].value); +} + +const struct adc_decoder_api __adc_default_decoder = { + .get_frame_count = get_frame_count, + .get_size_info = adc_natively_supported_channel_size_info, + .decode = decode, +}; diff --git a/include/zephyr/drivers/adc.h b/include/zephyr/drivers/adc.h index b5c1cec256cf5..58ee9417e693e 100644 --- a/include/zephyr/drivers/adc.h +++ b/include/zephyr/drivers/adc.h @@ -16,6 +16,8 @@ #include #include #include +#include +#include #ifdef __cplusplus extern "C" { @@ -688,6 +690,169 @@ struct adc_sequence { bool calibrate; }; +struct adc_data_header { + /** + * The closest timestamp for when the first frame was generated as attained by + * :c:func:`k_uptime_ticks`. + */ + uint64_t base_timestamp_ns; + /** + * The number of elements in the 'readings' array. + * + * This must be at least 1 + */ + uint16_t reading_count; +}; + +/** + * Data for the adc channel. + */ +struct adc_data { + struct adc_data_header header; + int8_t shift; + struct adc_sample_data { + uint32_t timestamp_delta; + union { + q31_t value; + }; + } readings[1]; +}; + +/** + * @brief ADC trigger types. + */ +enum adc_trigger_type { + /** Trigger fires whenever new data is ready. */ + ADC_TRIG_DATA_READY, + + /** Trigger fires when the FIFO watermark has been reached. */ + ADC_TRIG_FIFO_WATERMARK, + + /** Trigger fires when the FIFO becomes full. */ + ADC_TRIG_FIFO_FULL, + + /** + * Number of all common adc triggers. + */ + ADC_TRIG_COMMON_COUNT, + + /** + * This and higher values are adc specific. + * Refer to the adc header file. + */ + ADC_TRIG_PRIV_START = ADC_TRIG_COMMON_COUNT, + + /** + * Maximum value describing a adc trigger type. + */ + ADC_TRIG_MAX = INT16_MAX, +}; + +/** + * @brief Options for what to do with the associated data when a trigger is consumed + */ +enum adc_stream_data_opt { + /** @brief Include whatever data is associated with the trigger */ + ADC_STREAM_DATA_INCLUDE = 0, + /** @brief Do nothing with the associated trigger data, it may be consumed later */ + ADC_STREAM_DATA_NOP = 1, + /** @brief Flush/clear whatever data is associated with the trigger */ + ADC_STREAM_DATA_DROP = 2, +}; + +struct adc_stream_trigger { + enum adc_trigger_type trigger; + enum adc_stream_data_opt opt; +}; + +/** + * @brief ADC Channel Specification + * + * A ADC channel specification is a unique identifier per ADC device describing + * a measurement channel. + * + */ +struct adc_chan_spec { + uint8_t chan_idx; /**< A ADC channel index */ + uint8_t chan_resolution; /**< A ADC channel resolution */ +}; + +/* + * Internal data structure used to store information about the IODevice for async reading and + * streaming adc data. + */ +struct adc_read_config { + const struct device *adc; + const bool is_streaming; + const struct adc_dt_spec *adc_spec; + const struct adc_stream_trigger *triggers; + struct adc_sequence *sequence; + uint16_t fifo_watermark_lvl; + uint16_t fifo_mode; + size_t adc_spec_cnt; + size_t trigger_cnt; +}; + +/** + * @brief Decodes a single raw data buffer + * + */ +struct adc_decoder_api { + /** + * @brief Get the number of frames in the current buffer. + * + * @param[in] buffer The buffer provided on the @ref rtio context. + * @param[in] channel The channel to get the count for + * @param[out] frame_count The number of frames on the buffer (at least 1) + * @return 0 on success + * @return -ENOTSUP if the channel/channel_idx aren't found + */ + int (*get_frame_count)(const uint8_t *buffer, uint32_t channel, + uint16_t *frame_count); + + /** + * @brief Get the size required to decode a given channel + * + * When decoding a single frame, use @p base_size. For every additional frame, add another + * @p frame_size. As an example, to decode 3 frames use: 'base_size + 2 * frame_size'. + * + * @param[in] adc_spec ADC Specs + * @param[in] channel The channel to query + * @param[out] base_size The size of decoding the first frame + * @param[out] frame_size The additional size of every additional frame + * @return 0 on success + * @return -ENOTSUP if the channel is not supported + */ + int (*get_size_info)(struct adc_dt_spec adc_spec, uint32_t channel, size_t *base_size, + size_t *frame_size); + + /** + * @brief Decode up to @p max_count samples from the buffer + * + * Decode samples of channel across multiple frames. If there exist + * multiple instances of the same channel, @p channel_index is used to differentiate them. + * + * @param[in] buffer The buffer provided on the @ref rtio context + * @param[in] channel The channel to decode + * @param[in,out] fit The current frame iterator + * @param[in] max_count The maximum number of channels to decode. + * @param[out] data_out The decoded data + * @return 0 no more samples to decode + * @return >0 the number of decoded frames + * @return <0 on error + */ + int (*decode)(const uint8_t *buffer, uint32_t channel, uint32_t *fit, + uint16_t max_count, void *data_out); + + /** + * @brief Check if the given trigger type is present + * + * @param[in] buffer The buffer provided on the @ref rtio context + * @param[in] trigger The trigger type in question + * @return Whether the trigger is present in the buffer + */ + bool (*has_trigger)(const uint8_t *buffer, enum adc_trigger_type trigger); +}; /** * @brief Type definition of ADC API function for configuring a channel. @@ -703,6 +868,22 @@ typedef int (*adc_api_channel_setup)(const struct device *dev, typedef int (*adc_api_read)(const struct device *dev, const struct adc_sequence *sequence); +/** + * @brief Type definition of ADC API function for setting an submit + * stream request. + */ +typedef void (*adc_api_submit)(const struct device *dev, + struct rtio_iodev_sqe *sqe); + +/** + * @brief Get the decoder associate with the given device + * + * @see adc_get_decoder() for more details + */ +typedef int (*adc_api_get_decoder)(const struct device *dev, + const struct adc_decoder_api **api); + + /** * @brief Type definition of ADC API function for setting an asynchronous * read request. @@ -722,6 +903,10 @@ __subsystem struct adc_driver_api { adc_api_read read; #ifdef CONFIG_ADC_ASYNC adc_api_read_async read_async; +#endif +#ifdef CONFIG_ADC_STREAM + adc_api_submit submit; + adc_api_get_decoder get_decoder; #endif uint16_t ref_internal; /* mV */ }; @@ -834,6 +1019,21 @@ __syscall int adc_read_async(const struct device *dev, const struct adc_sequence *sequence, struct k_poll_signal *async); +/** + * @brief Get decoder APIs for that device. + * + * @note This function is available only if @kconfig{CONFIG_ADC_STREAM} + * is selected. + * + * @param dev Pointer to the device structure for the driver instance. + * @param api Pointer to the decoder which will be set upon success. + * + * @returns 0 on success, negative error code otherwise. + * + * + */ +__syscall int adc_get_decoder(const struct device *dev, + const struct adc_decoder_api **api); #ifdef CONFIG_ADC_ASYNC static inline int z_impl_adc_read_async(const struct device *dev, @@ -844,6 +1044,68 @@ static inline int z_impl_adc_read_async(const struct device *dev, } #endif /* CONFIG_ADC_ASYNC */ +#ifdef CONFIG_ADC_STREAM +/* + * Generic data structure used for encoding the sample timestamp and number of channels sampled. + */ +struct __attribute__((__packed__)) adc_data_generic_header { + /* The timestamp at which the data was collected from the adc */ + uint64_t timestamp_ns; + + /* + * The number of channels present in the frame. + */ + uint8_t num_channels; + + /* Shift value for all samples in the frame */ + int8_t shift; + + /* This padding is needed to make sure that the 'channels' field is aligned */ + int16_t _padding; + + /* Channels present in the frame */ + struct adc_chan_spec channels[0]; +}; + +static inline int adc_stream(struct rtio_iodev *iodev, struct rtio *ctx, void *userdata, + struct rtio_sqe **handle) +{ + if (IS_ENABLED(CONFIG_USERSPACE)) { + struct rtio_sqe sqe; + + rtio_sqe_prep_read_multishot(&sqe, iodev, RTIO_PRIO_NORM, userdata); + rtio_sqe_copy_in_get_handles(ctx, &sqe, handle, 1); + } else { + struct rtio_sqe *sqe = rtio_sqe_acquire(ctx); + + if (sqe == NULL) { + return -ENOMEM; + } + if (handle != NULL) { + *handle = sqe; + } + rtio_sqe_prep_read_multishot(sqe, iodev, RTIO_PRIO_NORM, userdata); + } + rtio_submit(ctx, 0); + return 0; +} + +static inline int z_impl_adc_get_decoder(const struct device *dev, + const struct adc_decoder_api **decoder) +{ + const struct adc_driver_api *api = DEVICE_API_GET(adc, dev); + + __ASSERT_NO_MSG(api != NULL); + + if (api->get_decoder == NULL) { + *decoder = NULL; + return -1; + } + + return api->get_decoder(dev, decoder); +} +#endif /* CONFIG_ADC_STREAM */ + /** * @brief Get the internal reference voltage. * @@ -1052,6 +1314,69 @@ static inline bool adc_is_ready_dt(const struct adc_dt_spec *spec) * @} */ +/** + * @brief Get the decoder name for the current driver + * + * This function depends on `DT_DRV_COMPAT` being defined. + */ +#define ADC_DECODER_NAME() UTIL_CAT(DT_DRV_COMPAT, __adc_decoder_api) + +/** + * @brief Statically get the decoder for a given node + * + * @code{.c} + * static const adc_decoder_api *decoder = ADC_DECODER_DT_GET(DT_ALIAS(adc)); + * @endcode + */ +#define ADC_DECODER_DT_GET(node_id) \ + &UTIL_CAT(DT_STRING_TOKEN_BY_IDX(node_id, compatible, 0), __adc_decoder_api) + +/** + * @brief Define a decoder API + * + * This macro should be created once per compatible string of a adc and will create a statically + * referenceable decoder API. + * + * @code{.c} + * ADC_DECODER_API_DT_DEFINE() = { + * .get_frame_count = my_driver_get_frame_count, + * .get_timestamp = my_driver_get_timestamp, + * .get_shift = my_driver_get_shift, + * .decode = my_driver_decode, + * }; + * @endcode + */ +#define ADC_DECODER_API_DT_DEFINE() \ + COND_CODE_1(DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT), (), (static)) \ + const STRUCT_SECTION_ITERABLE(adc_decoder_api, ADC_DECODER_NAME()) + +#define Z_MAYBE_ADC_DECODER_DECLARE_INTERNAL_IDX(node_id, prop, idx) \ + extern const struct adc_decoder_api UTIL_CAT( \ + DT_STRING_TOKEN_BY_IDX(node_id, prop, idx), __adc_decoder_api); + +#define Z_MAYBE_ADC_DECODER_DECLARE_INTERNAL(node_id) \ + COND_CODE_1(DT_NODE_HAS_PROP(node_id, compatible), \ + (DT_FOREACH_PROP_ELEM(node_id, compatible, \ + Z_MAYBE_ADC_DECODER_DECLARE_INTERNAL_IDX)), \ + ()) + +DT_FOREACH_STATUS_OKAY_NODE(Z_MAYBE_ADC_DECODER_DECLARE_INTERNAL) + +/* The default adc iodev API */ +extern const struct rtio_iodev_api __adc_iodev_api; + +#define ADC_DT_STREAM_IODEV(name, dt_node, adc_dt_spec, ...) \ + static struct adc_stream_trigger _CONCAT(__trigger_array_, name)[] = {__VA_ARGS__}; \ + static struct adc_read_config _CONCAT(__adc_read_config_, name) = { \ + .adc = DEVICE_DT_GET(dt_node), \ + .is_streaming = true, \ + .adc_spec = adc_dt_spec, \ + .triggers = _CONCAT(__trigger_array_, name), \ + .adc_spec_cnt = ARRAY_SIZE(adc_dt_spec), \ + .trigger_cnt = ARRAY_SIZE(_CONCAT(__trigger_array_, name)), \ + }; \ + RTIO_IODEV_DEFINE(name, &__adc_iodev_api, &_CONCAT(__adc_read_config_, name)) + #ifdef __cplusplus } #endif diff --git a/include/zephyr/linker/common-rom/common-rom-misc.ld b/include/zephyr/linker/common-rom/common-rom-misc.ld index 1559d1463ebd6..ebc239f96968e 100644 --- a/include/zephyr/linker/common-rom/common-rom-misc.ld +++ b/include/zephyr/linker/common-rom/common-rom-misc.ld @@ -22,6 +22,10 @@ ITERABLE_SECTION_ROM(sensor_decoder_api, Z_LINK_ITERABLE_SUBALIGN) #endif +#if defined(CONFIG_ADC_STREAM) + ITERABLE_SECTION_ROM(adc_decoder_api, Z_LINK_ITERABLE_SUBALIGN) +#endif + #if defined(CONFIG_MCUMGR) ITERABLE_SECTION_ROM(mcumgr_handler, Z_LINK_ITERABLE_SUBALIGN) #endif diff --git a/scripts/build/gen_kobject_list.py b/scripts/build/gen_kobject_list.py index 1d60c0df0fc9d..85d29eec13218 100755 --- a/scripts/build/gen_kobject_list.py +++ b/scripts/build/gen_kobject_list.py @@ -114,7 +114,8 @@ ("ztest_test_rule", ("CONFIG_ZTEST", True, False)), ("rtio", ("CONFIG_RTIO", False, False)), ("rtio_iodev", ("CONFIG_RTIO", False, False)), - ("sensor_decoder_api", ("CONFIG_SENSOR_ASYNC_API", True, False)) + ("sensor_decoder_api", ("CONFIG_SENSOR_ASYNC_API", True, False)), + ("adc_decoder_api", ("CONFIG_ADC_STREAM", True, False)) ]) def kobject_to_enum(kobj): From b4ca871c37d66a85158d2481439f21a276f8d25d Mon Sep 17 00:00:00 2001 From: Vladislav Pejic Date: Wed, 4 Jun 2025 15:13:27 +0200 Subject: [PATCH 2/6] boards: shields: EVAL-AD4052-ARDZ comp and overlay Fix for compatible property. Addition of hardware timer to be used with APARD32690 board. Signed-off-by: Vladislav Pejic --- .../boards/apard32690_max32690_m4.overlay | 20 +++++++++++++++++++ .../eval_ad4052_ardz/eval_ad4052_ardz.overlay | 10 ++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/boards/shields/eval_ad4052_ardz/boards/apard32690_max32690_m4.overlay b/boards/shields/eval_ad4052_ardz/boards/apard32690_max32690_m4.overlay index eadfaa8d4695c..90cb8cc7e08b3 100644 --- a/boards/shields/eval_ad4052_ardz/boards/apard32690_max32690_m4.overlay +++ b/boards/shields/eval_ad4052_ardz/boards/apard32690_max32690_m4.overlay @@ -9,3 +9,23 @@ io-channels = <&adc4052_eval_ad4052_ardz 0>; }; }; + +&arduino_spi { + adc4052_eval_ad4052_ardz: adc4052@0 { + conversion-gpios = <&arduino_header 12 (GPIO_ACTIVE_HIGH | MAX32_GPIO_VSEL_VDDIOH)>; + }; +}; + +&timer0 { + status = "okay"; + + counter0: counter { + status = "okay"; + }; +}; + +/ { + chosen { + zephyr,adc-clock = &counter0; + }; +}; diff --git a/boards/shields/eval_ad4052_ardz/eval_ad4052_ardz.overlay b/boards/shields/eval_ad4052_ardz/eval_ad4052_ardz.overlay index 856bdf9877c9e..6573b80ffd610 100644 --- a/boards/shields/eval_ad4052_ardz/eval_ad4052_ardz.overlay +++ b/boards/shields/eval_ad4052_ardz/eval_ad4052_ardz.overlay @@ -4,6 +4,12 @@ * SPDX-License-Identifier: Apache-2.0 */ +/ { + aliases { + adc0 = &adc4052_eval_ad4052_ardz; + }; +}; + &arduino_spi { status = "okay"; @@ -12,12 +18,12 @@ spi-max-frequency = ; gp1-gpios = <&arduino_header 14 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; gp0-gpios = <&arduino_header 15 (GPIO_PULL_DOWN)>; - conversion-gpios = <&arduino_header 12 (GPIO_ACTIVE_HIGH | MAX32_GPIO_VSEL_VDDIOH)>; + conversion-gpios = <&arduino_header 12 (GPIO_ACTIVE_HIGH)>; #address-cells = <1>; #size-cells = <0>; #io-channel-cells = <1>; status = "okay"; - compatible = "adi,ad405x-adc"; + compatible = "adi,ad4052-adc"; channel@0 { reg = <0>; From f231fa5eaa255a2853ab463631a3c3335d89ce7e Mon Sep 17 00:00:00 2001 From: Vladislav Pejic Date: Wed, 21 May 2025 13:12:48 +0200 Subject: [PATCH 3/6] driver: adc: ad405x: Add RTIO stream Added support for RTIO stream. Also added sampling_period property to the DTS. This is for setting the sampling period when streaming is used. Hardware or kernel timer can be used for this. To use hardware timer you need to add it to the DTS. Example: { chosen { zephyr,adc-clock = &counter0; }; }; Signed-off-by: Vladislav Pejic --- drivers/adc/Kconfig.ad405x | 7 + drivers/adc/adc_ad405x.c | 457 +++++++++++++++++++++- drivers/adc/adc_max32.c | 157 ++++++++ dts/bindings/adc/adi,ad405x-adc-base.yaml | 5 + 4 files changed, 613 insertions(+), 13 deletions(-) diff --git a/drivers/adc/Kconfig.ad405x b/drivers/adc/Kconfig.ad405x index 5abc89a8dd56f..7d804bab44c9a 100644 --- a/drivers/adc/Kconfig.ad405x +++ b/drivers/adc/Kconfig.ad405x @@ -11,6 +11,13 @@ config ADC_AD405X help Enable ADC driver for ADI AD405X. +config AD405X_STREAM + bool "Use FIFO to stream data" + select AD405X_TRIGGER + depends on SPI_RTIO + help + Use this configuration option to enable streaming ADC data via RTIO. + config AD405X_TRIGGER bool "AD405X interrupts" default n diff --git a/drivers/adc/adc_ad405x.c b/drivers/adc/adc_ad405x.c index d45355781e12f..8383c8d05a01f 100644 --- a/drivers/adc/adc_ad405x.c +++ b/drivers/adc/adc_ad405x.c @@ -73,6 +73,8 @@ LOG_MODULE_REGISTER(adc_ad405x, CONFIG_ADC_LOG_LEVEL); #define AD405X_SINGLE_ENDED 0x0U #define AD405X_DIFFERENTIAL BIT(7) +#define AD405X_NO_GPIO 0xFFU + /** AD405X_REG_TIMER_CONFIG Bit Definitions */ #define AD405X_FS_BURST_AUTO_MSK GENMASK(7, 4) @@ -151,6 +153,9 @@ struct adc_ad405x_config { struct gpio_dt_spec conversion; uint16_t chip_id; const struct adc_dt_spec spec; +#ifdef CONFIG_AD405X_STREAM + uint32_t sampling_period; +#endif /* CONFIG_AD405X_STREAM */ }; struct adc_ad405x_data { @@ -174,9 +179,94 @@ struct adc_ad405x_data { struct gpio_callback gpio0_cb; struct k_sem sem_drdy; uint8_t has_drdy; +#endif /* CONFIG_AD405X_TRIGGER */ +#ifdef CONFIG_AD405X_STREAM + struct rtio_iodev_sqe *sqe; + struct rtio *rtio_ctx; + struct rtio_iodev *iodev; + uint64_t timestamp; + struct rtio *r_cb; + uint32_t adc_sample; + uint8_t data_ready_gpio; +#if DT_HAS_CHOSEN(zephyr_adc_clock) + const struct device *timer_dev; +#else + struct k_timer sample_timer; #endif +#endif /* CONFIG_AD405X_STREAM */ +}; + +#ifdef CONFIG_AD405X_STREAM +#include + +#define AD405X_DEF_SAMPLING_PERIOD 10000U /* 10ms */ + +/** AD405X qscale modes */ +enum ad405x_qscale_modes { + AD4050_6_12B_MODE = 0, + AD4050_6_14B_MODE = 1, + AD4052_8_16B_MODE = 2, + AD4052_8_20B_MODE = 3, }; +struct adc_ad405x_fifo_data { + uint8_t is_fifo: 1; + uint8_t ad405x_qscale_mode: 2; + uint8_t diff_mode: 1; + uint8_t empty: 1; + uint8_t res: 3; + uint16_t vref_mv; + uint64_t timestamp; +} __attribute__((__packed__)); + +static void ad405x_stream_irq_handler(const struct device *dev); +static int ad405x_conv_start(const struct device *dev); + +#if DT_HAS_CHOSEN(zephyr_adc_clock) +static void timer_alarm_handler(const struct device *counter_dev, uint8_t chan_id, + uint32_t ticks, void *user_data) +{ + struct adc_ad405x_data *data = (struct adc_ad405x_data *)user_data; + + ad405x_conv_start(data->dev); +} +#else +static void sample_timer_handler(struct k_timer *timer) +{ + struct adc_ad405x_data *data = CONTAINER_OF(timer, struct adc_ad405x_data, + sample_timer); + + ad405x_conv_start(data->dev); +} +#endif +static void ad405x_timer_init(const struct device *dev) +{ + struct adc_ad405x_data *data = dev->data; + +#if DT_HAS_CHOSEN(zephyr_adc_clock) + counter_start(data->timer_dev); +#else + k_timer_init(&data->sample_timer, sample_timer_handler, NULL); +#endif +} + +static void ad405x_timer_start(const struct device *dev) +{ + struct adc_ad405x_data *data = dev->data; + const struct adc_ad405x_config *cfg_405 = (const struct adc_ad405x_config *)dev->config; +#if DT_HAS_CHOSEN(zephyr_adc_clock) + struct counter_alarm_cfg alarm_cfg = {.flags = 0, + .ticks = counter_us_to_ticks(data->timer_dev, (uint64_t)cfg_405->sampling_period), + .callback = timer_alarm_handler, + .user_data = data}; + + counter_set_channel_alarm(data->timer_dev, 0, &alarm_cfg); +#else + k_timer_start(&data->sample_timer, K_USEC(cfg_405->sampling_period), K_NO_WAIT); +#endif +} +#endif /* CONFIG_AD405X_STREAM */ + static bool ad405x_bus_is_ready_spi(const union ad405x_bus *bus) { bool ret = spi_is_ready_dt(&bus->spi); @@ -310,7 +400,17 @@ static void ad405x_gpio1_callback(const struct device *dev, struct gpio_callback k_sem_give(&drv_data->sem_devrdy); break; case AD405X_DATA_READY: +#ifdef CONFIG_AD405X_STREAM + const struct adc_read_config *cfg_adc = drv_data->sqe->sqe.iodev->data; + + if (cfg_adc->is_streaming) { + ad405x_stream_irq_handler(drv_data->dev); + } else { + k_sem_give(&drv_data->sem_drdy); + } +#else k_sem_give(&drv_data->sem_drdy); +#endif /* CONFIG_AD405X_STREAM */ gpio_flag = GPIO_INT_EDGE_TO_INACTIVE; break; default: /* TODO */ @@ -331,7 +431,17 @@ static void ad405x_gpio0_callback(const struct device *dev, struct gpio_callback switch (drv_data->gp0_mode) { case AD405X_DATA_READY: +#ifdef CONFIG_AD405X_STREAM + const struct adc_read_config *cfg_adc = drv_data->sqe->sqe.iodev->data; + + if (cfg_adc->is_streaming) { + ad405x_stream_irq_handler(drv_data->dev); + } else { + k_sem_give(&drv_data->sem_drdy); + } +#else k_sem_give(&drv_data->sem_drdy); +#endif /* CONFIG_AD405X_STREAM */ gpio_flag = GPIO_INT_EDGE_TO_INACTIVE; break; default: @@ -459,7 +569,7 @@ static int adc_ad405x_validate_buffer_size(const struct device *dev, return 0; } -int ad405x_conv_start(const struct device *dev) +static int ad405x_conv_start(const struct device *dev) { const struct adc_ad405x_config *cfg = dev->config; int ret; @@ -701,12 +811,18 @@ int ad405x_set_gpx_mode(const struct device *dev, uint8_t gp0_1, enum ad405x_gpx if (gpx_mode == AD405X_DATA_READY) { gpio_pin_interrupt_configure_dt(&cfg->gp0_interrupt, GPIO_INT_EDGE_TO_INACTIVE); +#ifdef CONFIG_AD405X_STREAM + data->data_ready_gpio = AD405X_GP0; +#endif /* CONFIG_AD405X_STREAM */ } data->gp0_mode = gpx_mode; } else { if (gpx_mode == AD405X_DATA_READY) { gpio_pin_interrupt_configure_dt(&cfg->gp1_interrupt, GPIO_INT_EDGE_TO_INACTIVE); +#ifdef CONFIG_AD405X_STREAM + data->data_ready_gpio = AD405X_GP1; +#endif /* CONFIG_AD405X_STREAM */ } data->gp1_mode = gpx_mode; } @@ -899,17 +1015,322 @@ static int adc_ad405x_init(const struct device *dev) return ret; } +#ifdef CONFIG_AD405X_STREAM +void ad405x_submit_stream(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe) +{ + struct adc_ad405x_data *data = (struct adc_ad405x_data *)dev->data; + const struct adc_ad405x_config *cfg_405 = (const struct adc_ad405x_config *)dev->config; + + if (data->data_ready_gpio > AD405X_GP1) { + LOG_ERR("DATA_READY irq is not enabled!"); + rtio_iodev_sqe_err(iodev_sqe, -ENOTSUP); + return; + } + + int rc; + + if (data->data_ready_gpio == AD405X_GP0) { + rc = gpio_pin_interrupt_configure_dt(&cfg_405->gp0_interrupt, + GPIO_INT_DISABLE); + } else { + rc = gpio_pin_interrupt_configure_dt(&cfg_405->gp1_interrupt, + GPIO_INT_DISABLE); + } + + if (rc < 0) { + rtio_iodev_sqe_err(iodev_sqe, rc); + return; + } + + if (data->operation_mode == AD405X_CONFIG_MODE_OP) { + rc = ad405x_set_operation_mode(dev, cfg_405->active_mode); + + if (rc < 0) { + LOG_ERR("Set operation mode failed!"); + return; + } + ad405x_timer_init(dev); + } + + if (data->data_ready_gpio == AD405X_GP0) { + rc = gpio_pin_interrupt_configure_dt(&cfg_405->gp0_interrupt, + GPIO_INT_EDGE_TO_INACTIVE); + } else { + rc = gpio_pin_interrupt_configure_dt(&cfg_405->gp1_interrupt, + GPIO_INT_EDGE_TO_INACTIVE); + } + + if (rc < 0) { + rtio_iodev_sqe_err(iodev_sqe, rc); + return; + } + + data->sqe = iodev_sqe; + ad405x_timer_start(dev); +} + +static const uint32_t adc_ad405x_resolution[] = { + [AD4050_6_12B_MODE] = 12, + [AD4050_6_14B_MODE] = 14, + [AD4052_8_16B_MODE] = 16, + [AD4052_8_20B_MODE] = 20, +}; + +static inline void adc_ad405x_convert_q31(q31_t *out, const uint8_t *buff, + enum ad405x_qscale_modes mode, uint8_t diff_mode, + uint16_t vref_mv, uint8_t adc_shift) +{ + int32_t data_in = 0; + uint32_t scale = BIT(adc_ad405x_resolution[mode]); + + /* In Differential mode, 1 bit is used for sign */ + if (diff_mode) { + scale = BIT(adc_ad405x_resolution[mode] - 1); + } + + uint32_t sensitivity = (vref_mv * (scale - 1)) / scale + * 1000 / scale; /* uV / LSB */ + + switch (mode) { + case AD4050_6_12B_MODE: + case AD4050_6_14B_MODE: + case AD4052_8_16B_MODE: + data_in = sys_get_be16(buff); + if (diff_mode && (data_in & (BIT(adc_ad405x_resolution[mode] - 1)))) { + data_in |= ~BIT_MASK(adc_ad405x_resolution[mode]); + } + break; + + case AD4052_8_20B_MODE: + data_in = sys_get_be24(buff); + if (diff_mode && (data_in & (BIT(adc_ad405x_resolution[mode] - 1)))) { + data_in |= ~BIT_MASK(adc_ad405x_resolution[mode]); + } + break; + + default: + data_in = sys_get_be16(buff); + break; + } + + *out = BIT(31 - adc_shift)/* scaling to q_31*/ * sensitivity / 1000000/*uV to V*/ * data_in; +} + +static int ad405x_decoder_get_frame_count(const uint8_t *buffer, uint32_t channel, + uint16_t *frame_count) +{ + const struct adc_ad405x_fifo_data *enc_data = (const struct adc_ad405x_fifo_data *)buffer; + + if (enc_data->empty) { + return -ENODATA; + } + + /* This adc does not have FIFO so it will stream one sample at a time */ + *frame_count = 1; + + return 0; +} + +static int ad405x_decoder_decode(const uint8_t *buffer, uint32_t channel, uint32_t *fit, + uint16_t max_count, void *data_out) +{ + const struct adc_ad405x_fifo_data *enc_data = (const struct adc_ad405x_fifo_data *)buffer; + + if (*fit > 0) { + return -ENOTSUP; + } + + struct adc_data *data = (struct adc_data *)data_out; + + memset(data, 0, sizeof(struct adc_data)); + + if (enc_data->empty) { + data->header.base_timestamp_ns = 0; + data->header.reading_count = 0; + return -ENODATA; + } + + data->header.base_timestamp_ns = enc_data->timestamp; + data->header.reading_count = 1; + + /* 32 is used because input parameter for __builtin_clz func is + * unsigneg int (32 bits) and func will consider any input value + * as 32 bit. + */ + data->shift = 32 - __builtin_clz(enc_data->vref_mv); + + buffer += sizeof(struct adc_ad405x_fifo_data); + + data->readings[0].timestamp_delta = 0; + adc_ad405x_convert_q31(&data->readings[0].value, buffer, enc_data->ad405x_qscale_mode, + enc_data->diff_mode, enc_data->vref_mv, data->shift); + + *fit = 1; + + return 0; +} + +static void ad405x_process_sample_cb(struct rtio *r, const struct rtio_sqe *sqe, void *arg) +{ + struct rtio_iodev_sqe *iodev_sqe = sqe->userdata; + + rtio_iodev_sqe_ok(iodev_sqe, 0); +} + +static void ad405x_stream_irq_handler(const struct device *dev) +{ + struct adc_ad405x_data *data = (struct adc_ad405x_data *)dev->data; + const struct adc_ad405x_config *cfg = (const struct adc_ad405x_config *)dev->config; + struct rtio_iodev_sqe *current_sqe = data->sqe; + uint32_t sample_size = 2; + enum ad405x_qscale_modes qscale_mode = AD4050_6_12B_MODE; + struct adc_read_config *read_config = (struct adc_read_config *)data->sqe->sqe.iodev->data; + + if (read_config == NULL) { + return; + } + + if (current_sqe == NULL) { + return; + } + + if (cfg->chip_id == AD4050_CHIP_ID) { + if ((cfg->active_mode == AD405X_BURST_AVERAGING_MODE_OP) + || (cfg->active_mode == AD405X_AVERAGING_MODE_OP)) { + qscale_mode = AD4050_6_14B_MODE; + } + } else { /* AD4052_CHIP_ID */ + if ((cfg->active_mode == AD405X_BURST_AVERAGING_MODE_OP) + || (cfg->active_mode == AD405X_AVERAGING_MODE_OP)) { + sample_size = 3; + qscale_mode = AD4052_8_20B_MODE; + } else { + qscale_mode = AD4052_8_16B_MODE; + } + } + +#if DT_HAS_CHOSEN(zephyr_adc_clock) + uint32_t ticks; + int ret = counter_get_value(data->timer_dev, &ticks); + + if (ret != 0) { + LOG_ERR("Failed to get timer value"); + data->timestamp = 0; + return; + } + + data->timestamp = (uint64_t)(counter_ticks_to_us(data->timer_dev, ticks)) + * 1000 /* uS to nS */; +#else + data->timestamp = k_ticks_to_ns_floor64(k_uptime_ticks()); +#endif + data->sqe = NULL; + + /* Not inherently an underrun/overrun as we may have a buffer to fill next time */ + if (current_sqe == NULL) { + LOG_ERR("No pending SQE"); + return; + } + + const size_t min_read_size = sizeof(struct adc_ad405x_fifo_data) + sample_size; + + uint8_t *buf; + uint32_t buf_len; + + if (rtio_sqe_rx_buf(current_sqe, min_read_size, min_read_size, &buf, &buf_len) != 0) { + rtio_iodev_sqe_err(current_sqe, -ENOMEM); + return; + } + + /* Read FIFO and call back to rtio with rtio_sqe completion */ + struct adc_ad405x_fifo_data *hdr = (struct adc_ad405x_fifo_data *)buf; + + hdr->is_fifo = 1; + hdr->timestamp = data->timestamp; + hdr->diff_mode = cfg->spec.channel_cfg.differential; + hdr->vref_mv = cfg->spec.vref_mv; + hdr->ad405x_qscale_mode = qscale_mode; + + uint8_t *read_buf = buf + sizeof(*hdr); + + if (read_config->trigger_cnt != 0) { + enum adc_stream_data_opt data_opt = read_config->triggers[0].opt; + + for (int i = 1; i < read_config->trigger_cnt; i++) { + data_opt = MIN(data_opt, read_config->triggers[i].opt); + } + + if (data_opt == ADC_STREAM_DATA_NOP || data_opt == ADC_STREAM_DATA_DROP) { + hdr->empty = 1; + } + } + + /* Setup new rtio chain to read the data and report then check the result */ + struct rtio_sqe *read_fifo_data = rtio_sqe_acquire(data->rtio_ctx); + struct rtio_sqe *complete_op = rtio_sqe_acquire(data->rtio_ctx); + + rtio_sqe_prep_read(read_fifo_data, data->iodev, RTIO_PRIO_NORM, read_buf, sample_size, + current_sqe); + read_fifo_data->flags = RTIO_SQE_CHAINED; + rtio_sqe_prep_callback(complete_op, ad405x_process_sample_cb, (void *)dev, current_sqe); + + rtio_submit(data->rtio_ctx, 0); +} + +static bool ad405x_decoder_has_trigger(const uint8_t *buffer, enum adc_trigger_type trigger) +{ + const struct adc_ad405x_fifo_data *data = (const struct adc_ad405x_fifo_data *)buffer; + + if (!data->is_fifo) { + return false; + } + + switch (trigger) { + /* This family of chips doesn't have FIFO so if there is a buffer trigger has happen. */ + case ADC_TRIG_DATA_READY: + case ADC_TRIG_FIFO_WATERMARK: + case ADC_TRIG_FIFO_FULL: + return true; + default: + return false; + } +} + +ADC_DECODER_API_DT_DEFINE() = { + .get_frame_count = ad405x_decoder_get_frame_count, + .decode = ad405x_decoder_decode, + .has_trigger = ad405x_decoder_has_trigger, +}; + +int ad405x_get_decoder(const struct device *dev, const struct adc_decoder_api **api) +{ + ARG_UNUSED(dev); + *api = &ADC_DECODER_NAME(); + + return 0; +} + +#endif /* CONFIG_AD405X_STREAM */ + static DEVICE_API(adc, ad405x_api_funcs) = { .channel_setup = ad405x_channel_setup, .read = ad405x_read, .ref_internal = 2500, #ifdef CONFIG_ADC_ASYNC .read_async = ad405x_adc_read_async, -#endif +#endif /* CONFIG_ADC_ASYNC */ +#ifdef CONFIG_AD405X_STREAM + .submit = ad405x_submit_stream, + .get_decoder = ad405x_get_decoder, +#endif /* CONFIG_AD405X_STREAM */ }; #define AD405X_SPI_CFG SPI_WORD_SET(8) | SPI_TRANSFER_MSB +#define AD405X_RTIO_DEFINE(inst) \ + SPI_DT_IODEV_DEFINE(ad405x_iodev_##inst, DT_DRV_INST(inst), AD405X_SPI_CFG, 0U); \ + RTIO_DEFINE(ad405x_rtio_ctx_##inst, 16, 16); + #define DT_INST_AD405X(inst, t) DT_INST(inst, adi_ad##t##_adc) #define AD405X_GPIO_PROPS1(n) \ @@ -927,17 +1348,27 @@ static DEVICE_API(adc, ad405x_api_funcs) = { (AD405X_GPIO_PROPS0(n))) #define AD405X_INIT(t, n) \ - static struct adc_ad405x_data ad##t##_data_##n = {}; \ - static const struct adc_ad405x_config ad##t##_config_##n = { \ - .bus = {.spi = SPI_DT_SPEC_GET(DT_INST_AD405X(n, t), AD405X_SPI_CFG, 0)}, \ - .conversion = GPIO_DT_SPEC_GET_BY_IDX(DT_INST_AD405X(n, t), conversion_gpios, 0),\ - IF_ENABLED(CONFIG_AD405X_TRIGGER, (AD405X_GPIO(t, n))) \ - .chip_id = t, \ - .active_mode = AD405X_SAMPLE_MODE_OP, \ - .spec = ADC_DT_SPEC_STRUCT(DT_INST(n, DT_DRV_COMPAT), 0) \ - }; \ - DEVICE_DT_DEFINE(DT_INST_AD405X(n, t), adc_ad405x_init, NULL, &ad##t##_data_##n, \ - &ad##t##_config_##n, POST_KERNEL, \ + IF_ENABLED(CONFIG_AD405X_STREAM, (AD405X_RTIO_DEFINE(n))); \ + static struct adc_ad405x_data ad##t##_data_##n = { \ + IF_ENABLED(CONFIG_AD405X_STREAM, (.rtio_ctx = &ad405x_rtio_ctx_##n, \ + .iodev = &ad405x_iodev_##n, .data_ready_gpio = AD405X_NO_GPIO, \ + COND_CODE_1(DT_HAS_CHOSEN(zephyr_adc_clock), \ + (.timer_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_adc_clock)),), \ + ()))) \ + }; \ + static const struct adc_ad405x_config ad##t##_config_##n = { \ + .bus = {.spi = SPI_DT_SPEC_GET(DT_INST_AD405X(n, t), AD405X_SPI_CFG, 0)}, \ + .conversion = GPIO_DT_SPEC_GET_BY_IDX(DT_INST_AD405X(n, t), \ + conversion_gpios, 0), \ + IF_ENABLED(CONFIG_AD405X_TRIGGER, (AD405X_GPIO(t, n))) \ + .chip_id = t, \ + .active_mode = AD405X_SAMPLE_MODE_OP, \ + .spec = ADC_DT_SPEC_STRUCT(DT_INST(n, DT_DRV_COMPAT), 0), \ + IF_ENABLED(CONFIG_AD405X_STREAM, (.sampling_period = \ + DT_INST_PROP_OR(n, sampling_period, AD405X_DEF_SAMPLING_PERIOD),)) \ + }; \ + DEVICE_DT_DEFINE(DT_INST_AD405X(n, t), adc_ad405x_init, NULL, &ad##t##_data_##n, \ + &ad##t##_config_##n, POST_KERNEL, \ CONFIG_ADC_INIT_PRIORITY, &ad405x_api_funcs); /* diff --git a/drivers/adc/adc_max32.c b/drivers/adc/adc_max32.c index af04b00f573ae..0584ed89a2eeb 100644 --- a/drivers/adc/adc_max32.c +++ b/drivers/adc/adc_max32.c @@ -150,6 +150,163 @@ static int adc_max32_read(const struct device *dev, const struct adc_sequence *s return ret; } +<<<<<<< Updated upstream +======= +#ifdef CONFIG_ADC_MAX32_STREAM +static int start_read_stream(const struct device *dev, const struct adc_sequence *seq) +{ + struct max32_adc_data *data = dev->data; + int ret = 0; + + if (seq->resolution != data->resolution) { + LOG_ERR("Unsupported resolution (%d)", seq->resolution); + return -ENOTSUP; + } + if (seq->channels == 0) { + return -EINVAL; + } + if ((data->channels & seq->channels) != seq->channels) { + return -EINVAL; + } + + ret = Wrap_MXC_ADC_AverageConfig(seq->oversampling); + if (ret != 0) { + return -EINVAL; + } + + data->ctx.asynchronous = 1; + data->sample_channels = seq->channels; + + /* Here we use regular cb that does nothing, it should be + * adc_complete_rtio_cb but the problem is dev struct + * cannot be passed to HAL without some big changes + * in the HAL layers, for now it is set like this + */ + ret = Wrap_MXC_ADC_StartConversionAsyncStream(&data->sample_channels, adc_complete_cb); + if (ret != 0) { + return -EINVAL; + } + + return adc_context_wait_for_completion(&data->ctx); +} + +void adc_max32_submit_stream(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe) +{ + struct max32_adc_data *data = (struct max32_adc_data *)dev->data; + const struct adc_sequence *sequence = (const struct adc_sequence *)iodev_sqe->sqe.userdata; + int rc; + + if (data->no_mem == 1) { + data->no_mem = 0; + return; + } + data->sqe = iodev_sqe; + + adc_context_lock(&data->ctx, false, NULL); + rc = start_read_stream(dev, sequence); + + adc_context_release(&data->ctx, rc); + + if (rc < 0) { + LOG_ERR("Error starting conversion (%d)", rc); + } + +} + +static const uint32_t adc_max32_resolution[] = { + [MAX32_12B_MODE] = 12, +}; + +static inline int adc_max32_convert_q31(q31_t *out, const uint8_t *buff, + enum max32_qscale_modes mode, uint8_t diff_mode, + uint16_t vref_mv, uint8_t adc_shift) +{ + int32_t data_in = 0; + uint32_t scale = BIT(adc_max32_resolution[mode]); + + /* No Differential mode */ + if (diff_mode) { + return -EINVAL; + } + + uint32_t sensitivity = (vref_mv * (scale - 1)) / scale + * 1000 / scale; /* uV / LSB */ + + if (mode == MAX32_12B_MODE) { + data_in = (buff[1] << 8) | buff[0]; + if (diff_mode && (data_in & (BIT(adc_max32_resolution[mode] - 1)))) { + data_in |= ~BIT_MASK(adc_max32_resolution[mode]); + } + } else { + data_in = sys_get_be16(buff); + } + + *out = BIT(31 - adc_shift) * sensitivity / 1000000 * data_in; + return 0; +} + +static int adc_max32_decoder_get_frame_count(const uint8_t *buffer, uint32_t channel, + uint16_t *frame_count) +{ + const struct adc_max32_fifo_data *data = (const struct adc_max32_fifo_data *)buffer; + + *frame_count = data->fifo_byte_count/ADC_MAX32_SAMPLE_SIZE; + + return 0; +} + +static int adc_max32_decoder_decode(const uint8_t *buffer, uint32_t channel, uint32_t *fit, + uint16_t max_count, void *data_out) +{ + const struct adc_max32_fifo_data *enc_data = (const struct adc_max32_fifo_data *)buffer; + const uint8_t *buffer_end = + buffer + sizeof(struct adc_max32_fifo_data) + enc_data->fifo_byte_count; + int count = 0; + uint8_t sample_num = 0; + + if (buffer_end <= (buffer + *fit + sizeof(struct adc_max32_fifo_data))) { + return 0; + } + + struct adc_data *data = (struct adc_data *)data_out; + + memset(data, 0, sizeof(struct adc_data)); + data->header.base_timestamp_ns = enc_data->timestamp; + data->header.reading_count = 1; + + /* 32 is used because input parameter for __builtin_clz func is + * unsigneg int (32 bits) and func will consider any input value + * as 32 bit. + */ + data->shift = 32 - __builtin_clz(enc_data->vref_mv); + + buffer += sizeof(struct adc_max32_fifo_data); + uint8_t sample_set_size = enc_data->sample_set_size; + /* Calculate which sample is decoded. */ + if (*fit) { + sample_num = *fit / sample_set_size; + } + + while (count < max_count && buffer < buffer_end) { + /* 125 KSPS - this can be calculated from + * cnt and idle dts parameters but it is hardcoded for now + */ + data->readings[count].timestamp_delta = sample_num * (UINT32_C(1000000000) / 62500); + adc_max32_convert_q31(&data->readings[count].value, (buffer + *fit), + enc_data->max32_qscale_mode, enc_data->diff_mode, + enc_data->vref_mv, data->shift); + + + sample_num++; + *fit += sample_set_size; + count++; + } + + return 0; +} +#endif /* CONFIG_ADC_MAX32_STREAM */ + +>>>>>>> Stashed changes #ifdef CONFIG_ADC_ASYNC static int adc_max32_read_async(const struct device *dev, const struct adc_sequence *seq, struct k_poll_signal *async) diff --git a/dts/bindings/adc/adi,ad405x-adc-base.yaml b/dts/bindings/adc/adi,ad405x-adc-base.yaml index 7a6fc10a039b3..9079956b73a63 100644 --- a/dts/bindings/adc/adi,ad405x-adc-base.yaml +++ b/dts/bindings/adc/adi,ad405x-adc-base.yaml @@ -35,5 +35,10 @@ properties: This option increases the range from 0V to 2 x Vref. Note that this requires VDD >= 2 x Vref. + sampling-period: + type: int + description: | + Specify ADC sampling period in uS. + io-channel-cells: - input From 2594cc137d1198a9558514f226d4434f21494d60 Mon Sep 17 00:00:00 2001 From: Dimitrije Lilic Date: Wed, 21 May 2025 12:15:26 +0200 Subject: [PATCH 4/6] drivers: adc: max32: Support for RTIO stream Updated MAX32 driver with RTIO stream functionality. Signed-off-by: Dimitrije Lilic --- drivers/adc/Kconfig.max32 | 7 ++ drivers/adc/adc_max32.c | 148 +++++++++++++++++++++++++++++++++++--- soc/adi/max32/Kconfig.soc | 4 ++ west.yml | 2 +- 4 files changed, 151 insertions(+), 10 deletions(-) diff --git a/drivers/adc/Kconfig.max32 b/drivers/adc/Kconfig.max32 index b38059c203519..7bdc2de728a24 100644 --- a/drivers/adc/Kconfig.max32 +++ b/drivers/adc/Kconfig.max32 @@ -10,3 +10,10 @@ config ADC_MAX32 select PINCTRL help Enable ADC driver for ADI MAX32xxx MCUs. + +config ADC_MAX32_STREAM + bool "Use FIFO to stream data" + select ADC_ASYNC + depends on HAS_ADC_MAX32_REVB_ME18 + help + Use this configuration option to enable streaming ADC data via RTIO. diff --git a/drivers/adc/adc_max32.c b/drivers/adc/adc_max32.c index 0584ed89a2eeb..3d0cb38f51486 100644 --- a/drivers/adc/adc_max32.c +++ b/drivers/adc/adc_max32.c @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include LOG_MODULE_REGISTER(adc_max32, CONFIG_ADC_LOG_LEVEL); @@ -24,6 +26,16 @@ LOG_MODULE_REGISTER(adc_max32, CONFIG_ADC_LOG_LEVEL); /* reference voltage for the ADC */ #define MAX32_ADC_VREF_MV DT_INST_PROP(0, vref_mv) +#define ADC_MAX32_INT_FIFO_LVL_MSK BIT(7) +#define ADC_MAX32_SAMPLE_SIZE 2 +#define ADC_MAX32_BYTE_COUNT 16 + +enum adc_max32_fifo_format { + ADC_MAX32_DATA_STATUS_FIFO, + ADC_MAX32_DATA_ONLY_FIFO, + ADC_MAX32_RAW_DATA_ONLY_FIFO, +}; + struct max32_adc_config { uint8_t channel_count; mxc_adc_regs_t *regs; @@ -44,8 +56,45 @@ struct max32_adc_data { uint32_t channels; uint32_t sample_channels; const uint8_t resolution; +#ifdef CONFIG_ADC_MAX32_STREAM + struct rtio_iodev_sqe *sqe; + struct rtio *rtio_ctx; + struct rtio_iodev *iodev; + uint64_t timestamp; + struct rtio *r_cb; + uint32_t adc_sample; + uint8_t data_ready_gpio; + uint8_t no_mem; + struct k_timer sample_timer; + const struct adc_sequence *sequence; + uint8_t fifo_full_irq; +#endif /* CONFIG_ADC_MAX32_STREAM */ +}; + + +#ifdef CONFIG_ADC_MAX32_STREAM +/** MAX32 qscale modes */ +enum max32_qscale_modes { + MAX32_12B_MODE = 0, +}; + +struct adc_max32_fifo_config { + enum adc_max32_fifo_format fifo_format; + uint16_t fifo_samples; }; +struct adc_max32_fifo_data { + uint16_t is_fifo: 1; + uint16_t max32_qscale_mode: 1; + uint16_t diff_mode: 1; + uint16_t res: 4; + uint16_t fifo_byte_count: 5; + uint16_t sample_set_size: 4; + uint16_t vref_mv; + uint64_t timestamp; +} __attribute__((__packed__)); +#endif /* CONFIG_ADC_MAX32_STREAM */ + #ifdef CONFIG_ADC_ASYNC static void adc_complete_cb(void *req, int error) { @@ -54,6 +103,16 @@ static void adc_complete_cb(void *req, int error) } #endif /* CONFIG_ADC_ASYNC */ +#ifdef CONFIG_ADC_MAX32_STREAM +static void adc_complete_rtio_cb(const struct device *dev) +{ + struct max32_adc_data *data = dev->data; + struct rtio_iodev_sqe *iodev_sqe = data->sqe; + + rtio_iodev_sqe_ok(iodev_sqe, 0); +} +#endif /* CONFIG_ADC_MAX32_STREAM */ + static void adc_max32_start_channel(const struct device *dev) { struct max32_adc_data *data = dev->data; @@ -150,8 +209,6 @@ static int adc_max32_read(const struct device *dev, const struct adc_sequence *s return ret; } -<<<<<<< Updated upstream -======= #ifdef CONFIG_ADC_MAX32_STREAM static int start_read_stream(const struct device *dev, const struct adc_sequence *seq) { @@ -193,7 +250,7 @@ static int start_read_stream(const struct device *dev, const struct adc_sequence void adc_max32_submit_stream(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe) { struct max32_adc_data *data = (struct max32_adc_data *)dev->data; - const struct adc_sequence *sequence = (const struct adc_sequence *)iodev_sqe->sqe.userdata; + const struct adc_read_config *read_cfg = iodev_sqe->sqe.iodev->data; int rc; if (data->no_mem == 1) { @@ -203,14 +260,13 @@ void adc_max32_submit_stream(const struct device *dev, struct rtio_iodev_sqe *io data->sqe = iodev_sqe; adc_context_lock(&data->ctx, false, NULL); - rc = start_read_stream(dev, sequence); + rc = start_read_stream(dev, read_cfg->sequence); adc_context_release(&data->ctx, rc); if (rc < 0) { LOG_ERR("Error starting conversion (%d)", rc); } - } static const uint32_t adc_max32_resolution[] = { @@ -296,17 +352,16 @@ static int adc_max32_decoder_decode(const uint8_t *buffer, uint32_t channel, uin enc_data->max32_qscale_mode, enc_data->diff_mode, enc_data->vref_mv, data->shift); - sample_num++; *fit += sample_set_size; count++; } return 0; + } #endif /* CONFIG_ADC_MAX32_STREAM */ ->>>>>>> Stashed changes #ifdef CONFIG_ADC_ASYNC static int adc_max32_read_async(const struct device *dev, const struct adc_sequence *seq, struct k_poll_signal *async) @@ -433,6 +488,73 @@ static int adc_max32_init(const struct device *dev) return 0; } +#ifdef CONFIG_ADC_MAX32_STREAM +static void adc_max32_rtio_isr(const struct device *dev) +{ + struct max32_adc_data *const data = dev->data; + uint32_t flags = MXC_ADC_GetFlags(); + uint32_t int_req = BIT(3); + + MXC_ADC_Handler(); + if (flags & int_req) { + MXC_ADC_Free(); + } + MXC_ADC_ClearFlags(flags); + + if (flags & WRAP_MXC_F_ADC_CONV_DONE_IF) { + + data->timestamp = k_ticks_to_ns_floor64(k_uptime_ticks()); + + const size_t min_read_size = 64; + + uint8_t *buf; + uint32_t buf_len; + + if (rtio_sqe_rx_buf(data->sqe, min_read_size, min_read_size, + &buf, &buf_len) != 0) { + data->no_mem = 1; + rtio_iodev_sqe_err(data->sqe, -ENOMEM); + return; + } + struct adc_max32_fifo_data *hdr = (struct adc_max32_fifo_data *)buf; + + hdr->is_fifo = 1; + hdr->timestamp = data->timestamp; + hdr->vref_mv = MAX32_ADC_VREF_MV; + hdr->max32_qscale_mode = MAX32_12B_MODE; + hdr->fifo_byte_count = ADC_MAX32_BYTE_COUNT; + hdr->sample_set_size = ADC_MAX32_SAMPLE_SIZE; + + uint8_t *read_buf = buf + sizeof(*hdr); + + Wrap_MXC_ADC_GetData((uint16_t **)&read_buf); + + if (data->sample_channels != 0) { + adc_max32_start_channel(dev); + } else { + Wrap_MXC_ADC_DisableConversion(); + adc_context_on_sampling_done(&data->ctx, dev); + } + } + if (flags & int_req) { + + adc_complete_rtio_cb(dev); + } +} + +ADC_DECODER_API_DT_DEFINE() = { + .get_frame_count = adc_max32_decoder_get_frame_count, + .decode = adc_max32_decoder_decode, +}; + +int adc_max32_get_decoder(const struct device *dev, const struct adc_decoder_api **api) +{ + ARG_UNUSED(dev); + *api = &ADC_DECODER_NAME(); + + return 0; +} +#else static void adc_max32_isr(const struct device *dev) { struct max32_adc_data *const data = dev->data; @@ -452,6 +574,7 @@ static void adc_max32_isr(const struct device *dev) } } } +#endif /* CONFIG_ADC_MAX32_STREAM */ static DEVICE_API(adc, adc_max32_driver_api) = { .channel_setup = adc_max32_channel_setup, @@ -460,14 +583,21 @@ static DEVICE_API(adc, adc_max32_driver_api) = { .read_async = adc_max32_read_async, #endif /* CONFIG_ADC_ASYNC */ .ref_internal = MAX32_ADC_VREF_MV, +#ifdef CONFIG_ADC_MAX32_STREAM + .submit = adc_max32_submit_stream, + .get_decoder = adc_max32_get_decoder, +#endif /* CONFIG_ADC_MAX32_STREAM */ }; #define MAX32_ADC_INIT(_num) \ PINCTRL_DT_INST_DEFINE(_num); \ static void max32_adc_irq_init_##_num(void) \ { \ - IRQ_CONNECT(DT_INST_IRQN(_num), DT_INST_IRQ(_num, priority), adc_max32_isr, \ - DEVICE_DT_INST_GET(_num), 0); \ + COND_CODE_1(CONFIG_ADC_MAX32_STREAM, \ + (IRQ_CONNECT(DT_INST_IRQN(_num), DT_INST_IRQ(_num, priority), adc_max32_rtio_isr, \ + DEVICE_DT_INST_GET(_num), 0)), \ + (IRQ_CONNECT(DT_INST_IRQN(_num), DT_INST_IRQ(_num, priority), adc_max32_isr, \ + DEVICE_DT_INST_GET(_num), 0))); \ irq_enable(DT_INST_IRQN(_num)); \ }; \ static const struct max32_adc_config max32_adc_config_##_num = { \ diff --git a/soc/adi/max32/Kconfig.soc b/soc/adi/max32/Kconfig.soc index e4b2347ca0218..a56c32955e462 100644 --- a/soc/adi/max32/Kconfig.soc +++ b/soc/adi/max32/Kconfig.soc @@ -69,8 +69,12 @@ config SOC_MAX32680_M4 select SOC_MAX32680 select SOC_FAMILY_MAX32_M4 +config HAS_ADC_MAX32_REVB_ME18 + bool "revb" + config SOC_MAX32690 bool + select HAS_ADC_MAX32_REVB_ME18 config SOC_MAX32690_M4 bool diff --git a/west.yml b/west.yml index fe8253bfa2f22..44451584d229e 100644 --- a/west.yml +++ b/west.yml @@ -144,7 +144,7 @@ manifest: groups: - fs - name: hal_adi - revision: 16829b77264678f31a2d077a870af7bdca2d39bd + revision: pull/29/head path: modules/hal/adi groups: - hal From e4c5f4a042782acc2692fb0f2b9094f16fe0ee15 Mon Sep 17 00:00:00 2001 From: Vladislav Pejic Date: Wed, 4 Jun 2025 15:22:18 +0200 Subject: [PATCH 5/6] boards: adi: apard32690: Fix for gpio-map Fix for gpio-map-mask and gpio-map-pass-thru to enable use of MAX32_GPIO_VSEL_VDDIOH and other defines with arduino_header connector. Signed-off-by: Vladislav Pejic --- boards/adi/apard32690/apard32690_max32690_m4.dts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/boards/adi/apard32690/apard32690_max32690_m4.dts b/boards/adi/apard32690/apard32690_max32690_m4.dts index f291e50a80689..b78c0d0667976 100644 --- a/boards/adi/apard32690/apard32690_max32690_m4.dts +++ b/boards/adi/apard32690/apard32690_max32690_m4.dts @@ -58,8 +58,8 @@ arduino_header: connector { compatible = "arduino-header-r3"; #gpio-cells = <2>; - gpio-map-mask = <0xffffffff 0xffffffc0>; - gpio-map-pass-thru = <0 0x3f>; + gpio-map-mask = <0xffffffff 0xfffffe00>; + gpio-map-pass-thru = <0 0x1ff>; gpio-map = <0 0 &gpio3 0 0>, /* A0 */ <1 0 &gpio3 1 0>, /* A1 */ <2 0 &gpio3 2 0>, /* A2 */ From e4b654de68559f9797abb59e5d4228c0c8de31b7 Mon Sep 17 00:00:00 2001 From: Vladislav Pejic Date: Wed, 4 Jun 2025 15:29:15 +0200 Subject: [PATCH 6/6] sample: adc_stream: Add sample app for ADC stream Add sample application to demonstrate new ADC stream APIs. Two tests are also added: - ad4052-stream - max32-stream Signed-off-by: Vladislav Pejic --- samples/drivers/adc/adc_stream/CMakeLists.txt | 8 + samples/drivers/adc/adc_stream/README.rst | 42 +++++ .../drivers/adc/adc_stream/ad4052-stream.conf | 7 + .../apard32690_max32690_m4_ad4052.overlay | 11 ++ .../apard32690_max32690_m4_max32.overlay | 44 +++++ .../drivers/adc/adc_stream/max32-stream.conf | 3 + samples/drivers/adc/adc_stream/prj.conf | 3 + samples/drivers/adc/adc_stream/sample.yaml | 25 +++ samples/drivers/adc/adc_stream/src/main.c | 154 ++++++++++++++++++ 9 files changed, 297 insertions(+) create mode 100644 samples/drivers/adc/adc_stream/CMakeLists.txt create mode 100644 samples/drivers/adc/adc_stream/README.rst create mode 100644 samples/drivers/adc/adc_stream/ad4052-stream.conf create mode 100644 samples/drivers/adc/adc_stream/boards/apard32690_max32690_m4_ad4052.overlay create mode 100644 samples/drivers/adc/adc_stream/boards/apard32690_max32690_m4_max32.overlay create mode 100644 samples/drivers/adc/adc_stream/max32-stream.conf create mode 100644 samples/drivers/adc/adc_stream/prj.conf create mode 100644 samples/drivers/adc/adc_stream/sample.yaml create mode 100644 samples/drivers/adc/adc_stream/src/main.c diff --git a/samples/drivers/adc/adc_stream/CMakeLists.txt b/samples/drivers/adc/adc_stream/CMakeLists.txt new file mode 100644 index 0000000000000..ebbcd1526db74 --- /dev/null +++ b/samples/drivers/adc/adc_stream/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(adc_stream) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/drivers/adc/adc_stream/README.rst b/samples/drivers/adc/adc_stream/README.rst new file mode 100644 index 0000000000000..a00570ea2a7ae --- /dev/null +++ b/samples/drivers/adc/adc_stream/README.rst @@ -0,0 +1,42 @@ +.. zephyr:code-sample:: adc_stream + :name: Generic ADC stream + :relevant-api: adc_interface + + Get data from a ADC using stream. + +Overview +******** + +This sample application demonstrates how to use ADC stream APIs. + +Building and Running +******************** + +This sample supports one ADC. ADC needs to be aliased as ``adc0`` in devicetree. +For example: + +.. code-block:: devicetree + + / { + aliases { + adc0 = &ad4052; + }; + }; + +Make sure the aliase are in devicetree, then build and run with: + +.. zephyr-app-commands:: + :zephyr-app: samples/drivers/adc/adc_stream + :board: + :goals: build flash + :compact: + +Sample Output +============= + +.. code-block:: console + + ADC data for adc405@0 (0.000074) 942995000ns + ADC data for adc405@0 (0.000446) 963059000ns + ADC data for adc405@0 (0.000297) 983124000ns + ADC data for adc405@0 (0.000446) 1003189000ns diff --git a/samples/drivers/adc/adc_stream/ad4052-stream.conf b/samples/drivers/adc/adc_stream/ad4052-stream.conf new file mode 100644 index 0000000000000..27d4b451f7ba9 --- /dev/null +++ b/samples/drivers/adc/adc_stream/ad4052-stream.conf @@ -0,0 +1,7 @@ +# Copyright (c) 2025 Analog Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 +CONFIG_SPI=y +CONFIG_SPI_RTIO=y +CONFIG_AD405X_STREAM=y +CONFIG_COUNTER=y +CONFIG_COUNTER_TIMER_MAX32=y diff --git a/samples/drivers/adc/adc_stream/boards/apard32690_max32690_m4_ad4052.overlay b/samples/drivers/adc/adc_stream/boards/apard32690_max32690_m4_ad4052.overlay new file mode 100644 index 0000000000000..d408d245757e9 --- /dev/null +++ b/samples/drivers/adc/adc_stream/boards/apard32690_max32690_m4_ad4052.overlay @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2025 Analog Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&arduino_spi { + adc4052_eval_ad4052_ardz: adc4052@0 { + sampling-period = <20000>; /*uS*/ + }; +}; diff --git a/samples/drivers/adc/adc_stream/boards/apard32690_max32690_m4_max32.overlay b/samples/drivers/adc/adc_stream/boards/apard32690_max32690_m4_max32.overlay new file mode 100644 index 0000000000000..2f023fa7893f6 --- /dev/null +++ b/samples/drivers/adc/adc_stream/boards/apard32690_max32690_m4_max32.overlay @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025 Analog Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + adc0 = &adc; + }; + + zephyr,user { + io-channels = <&adc 0>; + }; +}; + +&adc_clk_ext_p0_9 { + power-source = ; +}; + +&adc { + reg = <0x40034000 0x1000>; + pinctrl-0 = <&adc_clk_ext_p0_9>; + pinctrl-names = "default"; + + /* ADC parameters set up 25KSPS sampling rate */ + track-count = <96>; + idle-count = <183>; + + #address-cells = <1>; + #size-cells = <0>; + #io-channel-cells = <1>; + status = "okay"; + compatible = "adi,max32-adc"; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + zephyr,vref-mv = <1250>; + }; +}; diff --git a/samples/drivers/adc/adc_stream/max32-stream.conf b/samples/drivers/adc/adc_stream/max32-stream.conf new file mode 100644 index 0000000000000..2fe955b1aa11e --- /dev/null +++ b/samples/drivers/adc/adc_stream/max32-stream.conf @@ -0,0 +1,3 @@ +# Copyright (c) 2025 Analog Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 +CONFIG_ADC_MAX32_STREAM=y diff --git a/samples/drivers/adc/adc_stream/prj.conf b/samples/drivers/adc/adc_stream/prj.conf new file mode 100644 index 0000000000000..504d2f5e81f76 --- /dev/null +++ b/samples/drivers/adc/adc_stream/prj.conf @@ -0,0 +1,3 @@ +CONFIG_ADC=y +CONFIG_GPIO=y +CONFIG_ADC_STREAM=y diff --git a/samples/drivers/adc/adc_stream/sample.yaml b/samples/drivers/adc/adc_stream/sample.yaml new file mode 100644 index 0000000000000..5283f9b6d828f --- /dev/null +++ b/samples/drivers/adc/adc_stream/sample.yaml @@ -0,0 +1,25 @@ +sample: + name: ADC stream sample +common: + tags: adc + harness: console + harness_config: + type: one_line + regex: + - "^ADC data for [^@]+@\\d+ \\([0-9.]+\\) \\d+n$" +tests: + sample.driver.adc_stream: + filter: dt_alias_exists("adc0") + sample.driver.adc_stream.ad4052-stream: + extra_args: + - SHIELD=eval_ad4052_ardz + - EXTRA_CONF_FILE=ad4052-stream.conf + - DTC_OVERLAY_FILE=boards/apard32690_max32690_m4_ad4052.overlay + platform_allow: + - apard32690/max32690/m4 + sample.driver.adc_stream.max32-stream: + extra_args: + - EXTRA_CONF_FILE=max32-stream.conf + - DTC_OVERLAY_FILE=boards/apard32690_max32690_m4_max32.overlay + platform_allow: + - apard32690/max32690/m4 diff --git a/samples/drivers/adc/adc_stream/src/main.c b/samples/drivers/adc/adc_stream/src/main.c new file mode 100644 index 0000000000000..a24866581819c --- /dev/null +++ b/samples/drivers/adc/adc_stream/src/main.c @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2024 Centro de Inovacao EDGE + * Copyright (c) 2025 Analog Devices, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +/* ADC node from the devicetree. */ +#define SAMPLE_ADC_NODE DT_ALIAS(adc0) + +#define SAMPLE_DT_SPEC_AND_COMMA(node_id, prop, idx) ADC_DT_SPEC_GET_BY_IDX(node_id, idx), + +#if DT_NODE_HAS_PROP(DT_PATH(zephyr_user), io_channels) +/* Data of ADC io-channels specified in devicetree. */ +static const struct adc_dt_spec adc_channels[] = { + DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels, SAMPLE_DT_SPEC_AND_COMMA) +}; +static const int adc_channels_count = ARRAY_SIZE(adc_channels); +#endif + +static void init_adc(void) +{ + int i, ret; + + ret = adc_is_ready_dt(&adc_channels[0]); + + for (i = 0; i < adc_channels_count; i++) { + ret = adc_channel_setup_dt(&adc_channels[i]); + } +} + +/* Define triggers for ADC stream. Trigger stands for an event that + * will cause the ADC to read data and perform an operation on it. + * + * Format for each trigger is: + * {trigger, operation to be done on the data associated to the trigger} + * + * For more information about supported triggers and data operations, + * refer to the enums adc_trigger_type and adc_stream_data_opt. + */ +#define SAMPLE_ADC_TRIGGERS \ + {ADC_TRIG_FIFO_FULL, ADC_STREAM_DATA_INCLUDE}, \ + {ADC_TRIG_FIFO_WATERMARK, ADC_STREAM_DATA_INCLUDE} + +ADC_DT_STREAM_IODEV(iodev, SAMPLE_ADC_NODE, adc_channels, SAMPLE_ADC_TRIGGERS); + +/* Mempool is used for sharing ADC data that has been read between ADC driver and the application. + * Data read in the ADC driver is stored in the mempool and can be processed in the application. + * Current values are set to support range of ADC drivers and can be optimized for smaller memory + * footprint. sizeof(void *) is used so memory blocks are properly aligned for different + * platforms. + */ +RTIO_DEFINE_WITH_MEMPOOL(adc_ctx, 16, 16, 20, 256, sizeof(void *)); + +static int print_adc_stream(const struct device *adc, struct rtio_iodev *local_iodev) +{ + int rc = 0; + const struct adc_decoder_api *decoder; + struct rtio_cqe *cqe; + uint8_t *buf; + uint32_t buf_len; + struct rtio_sqe *handles; + + /* Start the streams */ + adc_stream(local_iodev, &adc_ctx, NULL, &handles); + + while (1) { + /* CQE is a RTIO completion event. It is created in a ADC driver that is + * doing the streaming when there is a batch of data to be processed + * or some error occurred during ADC streaming. CQE contains result of + * data reading and userdata (if one is passed in adc_stream). + * It is used to retrieve mempool buffer where data is stored. + */ + cqe = rtio_cqe_consume_block(&adc_ctx); + + if (cqe->result != 0) { + printk("async read failed %d\n", cqe->result); + return cqe->result; + } + + rc = rtio_cqe_get_mempool_buffer(&adc_ctx, cqe, &buf, &buf_len); + + if (rc != 0) { + printk("get mempool buffer failed %d\n", rc); + return rc; + } + + rtio_cqe_release(&adc_ctx, cqe); + + rc = adc_get_decoder(adc, &decoder); + + if (rc != 0) { + printk("sensor_get_decoder failed %d\n", rc); + return rc; + } + + /* Frame iterator values when data comes from a FIFO */ + uint32_t adc_fit = 0; + struct adc_data adc_data = {0}; + + /* Number of accelerometer data frames */ + uint16_t frame_count; + + rc = decoder->get_frame_count(buf, 0, &frame_count); + + if (rc != 0) { + printk("get_frame_count failed %d\n", rc); + return rc; + } + + /* Decode all available accelerometer sample frames */ + for (int i = 0; i < frame_count; i++) { + decoder->decode(buf, 0, &adc_fit, 1, &adc_data); + + printk("ADC data for %s (%" PRIq(6) ") %lluns\n", adc->name, + PRIq_arg(adc_data.readings[0].value, 6, adc_data.shift), + (adc_data.header.base_timestamp_ns + + adc_data.readings[0].timestamp_delta)); + } + + rtio_release_buffer(&adc_ctx, buf, buf_len); + } + + return rc; +} + +int main(void) +{ + int ret; + struct adc_sequence sequence; + struct adc_read_config *read_cfg = iodev.data; + + read_cfg->sequence = &sequence; + + init_adc(); + ret = adc_sequence_init_dt(&adc_channels[0], &sequence); + if (ret < 0) { + printk("Failed to initialize ADC sequence: %d\n", ret); + return 0; + } + + print_adc_stream(adc_channels[0].dev, &iodev); + + return 0; +}