From 8f1ffc1c3d150944de60659f23a1b6fb26baddc9 Mon Sep 17 00:00:00 2001 From: Jakub Zymelka Date: Tue, 5 Aug 2025 13:55:10 +0200 Subject: [PATCH] [nrf fromlist] drivers: adc: nrfx_saadc: Add support for SAADC internal sampling timer The SAMPLERATE register can be used as a local timer instead of triggering individual SAMPLE tasks. When SAMPLERATE.MODE is set to Timers, it is sufficient to trigger SAMPLE task only once in order to start the SAADC and triggering the STOP task will stop sampling. The SAMPLERATE.CC field controls the sample rate. The SAMPLERATE timer should not be combined with SCAN mode and only one channel should be enabled when using the internal timer. Upstream PR #: 91368 Signed-off-by: Jakub Zymelka --- drivers/adc/adc_nrfx_saadc.c | 176 ++++++++++++++++++++++++++++------- 1 file changed, 144 insertions(+), 32 deletions(-) diff --git a/drivers/adc/adc_nrfx_saadc.c b/drivers/adc/adc_nrfx_saadc.c index 7d442b482e6..94af0cddac3 100644 --- a/drivers/adc/adc_nrfx_saadc.c +++ b/drivers/adc/adc_nrfx_saadc.c @@ -4,7 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -#define ADC_CONTEXT_USES_KERNEL_TIMER #include "adc_context.h" #include #include @@ -104,15 +103,20 @@ struct driver_data { uint8_t active_channel_cnt; void *mem_reg; void *user_buffer; + struct k_timer timer; + bool internal_timer_enabled; }; static struct driver_data m_data = { - ADC_CONTEXT_INIT_TIMER(m_data, ctx), ADC_CONTEXT_INIT_LOCK(m_data, ctx), ADC_CONTEXT_INIT_SYNC(m_data, ctx), .mem_reg = DMM_DEV_TO_REG(DT_NODELABEL(adc)), + .internal_timer_enabled = false, }; +/* Maximum value of the internal timer interval in microseconds. */ +#define ADC_INTERNAL_TIMER_INTERVAL_MAX_US 128U + /* Forward declaration */ static void event_handler(const nrfx_saadc_evt_t *event); @@ -385,22 +389,57 @@ static void adc_context_start_sampling(struct adc_context *ctx) static void adc_context_update_buffer_pointer(struct adc_context *ctx, bool repeat) { - void *samples_buffer; + if (!m_data.internal_timer_enabled) { + void *samples_buffer; + + if (!repeat) { + m_data.user_buffer = + (uint16_t *)m_data.user_buffer + m_data.active_channel_cnt; + } + + int error = dmm_buffer_in_prepare( + m_data.mem_reg, m_data.user_buffer, + NRFX_SAADC_SAMPLES_TO_BYTES(m_data.active_channel_cnt), &samples_buffer); + if (error != 0) { + LOG_ERR("DMM buffer allocation failed err=%d", error); + adc_context_complete(ctx, -EIO); + } - if (!repeat) { - m_data.user_buffer = (uint16_t *)m_data.user_buffer + m_data.active_channel_cnt; + nrfx_err_t nrfx_err = + nrfx_saadc_buffer_set(samples_buffer, m_data.active_channel_cnt); + if (nrfx_err != NRFX_SUCCESS) { + LOG_ERR("Failed to set buffer: %08x", nrfx_err); + adc_context_complete(ctx, -EIO); + } } +} - int error = dmm_buffer_in_prepare( - m_data.mem_reg, m_data.user_buffer, - NRFX_SAADC_SAMPLES_TO_BYTES(m_data.active_channel_cnt), - &samples_buffer); - if (error != 0) { - LOG_ERR("DMM buffer allocation failed err=%d", error); - adc_context_complete(ctx, -EIO); +static inline void adc_context_enable_timer(struct adc_context *ctx) +{ + if (!m_data.internal_timer_enabled) { + k_timer_start(&m_data.timer, K_NO_WAIT, K_USEC(ctx->options.interval_us)); + } else { + nrfx_err_t ret = nrfx_saadc_mode_trigger(); + + if (ret != NRFX_SUCCESS) { + LOG_ERR("Cannot start sampling: %d", ret); + adc_context_complete(&m_data.ctx, -EIO); + } } +} + +static inline void adc_context_disable_timer(struct adc_context *ctx) +{ + if (!m_data.internal_timer_enabled) { + k_timer_stop(&m_data.timer); + } +} + +static void adc_context_on_timer_expired(struct k_timer *timer_id) +{ + ARG_UNUSED(timer_id); - nrfx_saadc_buffer_set(samples_buffer, m_data.active_channel_cnt); + adc_context_request_next_sampling(&m_data.ctx); } static int get_resolution(const struct adc_sequence *sequence, nrf_saadc_resolution_t *resolution) @@ -490,31 +529,68 @@ static int check_buffer_size(const struct adc_sequence *sequence, uint8_t active return 0; } +static inline void single_ended_channel_cut_negative_sample(uint16_t channel_bit, + uint8_t single_ended_channels, + int16_t **sample) +{ + if ((channel_bit & single_ended_channels) && (*sample < 0)) { + **sample = 0; + } + + (*sample)++; +} + static bool has_single_ended(const struct adc_sequence *sequence) { return sequence->channels & m_data.single_ended_channels; } -static void correct_single_ended(const struct adc_sequence *sequence, nrf_saadc_value_t *buffer) +static void correct_single_ended(const struct adc_sequence *sequence, nrf_saadc_value_t *buffer, + uint16_t buffer_size) { - uint16_t channel_bit = BIT(0); uint8_t selected_channels = sequence->channels; uint8_t single_ended_channels = m_data.single_ended_channels; int16_t *sample = (int16_t *)buffer; - while (channel_bit <= single_ended_channels) { - if (channel_bit & selected_channels) { - if ((channel_bit & single_ended_channels) && (*sample < 0)) { - *sample = 0; + for (uint16_t channel_bit = BIT(0); channel_bit <= single_ended_channels; + channel_bit <<= 1) { + if (!(channel_bit & selected_channels)) { + continue; + } + + if (m_data.internal_timer_enabled) { + if (!(channel_bit & single_ended_channels)) { + continue; } - sample++; + for (int i = 0; i < buffer_size; i++) { + if (sample[i] < 0) { + sample[i] = 0; + } + } + } else { + single_ended_channel_cut_negative_sample(channel_bit, single_ended_channels, + &sample); } - - channel_bit <<= 1; } } +/* The internal timer runs at 16 MHz, so to convert the interval in microseconds + * to the internal timer CC value, we can use the formula: + * interval_cc = interval_us * 16 MHz + * where 16 MHz is the frequency of the internal timer. + * + * The maximum value for interval_cc is 2047, which corresponds to + * approximately 7816 Hz ~ 128us. + * The minimum value for interval_cc is depends on the SoC. + */ +static inline uint16_t interval_to_cc(uint16_t interval_us) +{ + NRFX_ASSERT((interval_us <= ADC_INTERNAL_TIMER_INTERVAL_MAX_US) && (interval_us > 0)); + + return (interval_us * 16) - 1; +} + static int start_read(const struct device *dev, const struct adc_sequence *sequence) { @@ -562,10 +638,28 @@ static int start_read(const struct device *dev, return error; } - nrfx_err = nrfx_saadc_simple_mode_set(selected_channels, - resolution, - oversampling, - event_handler); + if ((active_channel_cnt == 1) && (sequence->options != NULL) && + (sequence->options->interval_us <= ADC_INTERNAL_TIMER_INTERVAL_MAX_US) && + (sequence->options->interval_us > 0)) { + + nrfx_saadc_adv_config_t adv_config = { + .oversampling = oversampling, + .burst = NRF_SAADC_BURST_DISABLED, + .internal_timer_cc = interval_to_cc(sequence->options->interval_us), + .start_on_end = true, + }; + + m_data.internal_timer_enabled = true; + + nrfx_err = nrfx_saadc_advanced_mode_set(selected_channels, resolution, &adv_config, + event_handler); + } else { + m_data.internal_timer_enabled = false; + + nrfx_err = nrfx_saadc_simple_mode_set(selected_channels, resolution, oversampling, + event_handler); + } + if (nrfx_err != NRFX_SUCCESS) { return -EINVAL; } @@ -578,9 +672,11 @@ static int start_read(const struct device *dev, m_data.active_channel_cnt = active_channel_cnt; m_data.user_buffer = sequence->buffer; - error = dmm_buffer_in_prepare(m_data.mem_reg, - m_data.user_buffer, - NRFX_SAADC_SAMPLES_TO_BYTES(active_channel_cnt), + error = dmm_buffer_in_prepare(m_data.mem_reg, m_data.user_buffer, + (m_data.internal_timer_enabled + ? (NRFX_SAADC_SAMPLES_TO_BYTES(active_channel_cnt) * + (1 + sequence->options->extra_samplings)) + : NRFX_SAADC_SAMPLES_TO_BYTES(active_channel_cnt)), &samples_buffer); if (error != 0) { LOG_ERR("DMM buffer allocation failed err=%d", error); @@ -590,7 +686,15 @@ static int start_read(const struct device *dev, /* Buffer is filled in chunks, each chunk composed of number of samples equal to number * of active channels. Buffer pointer is advanced and reloaded after each chunk. */ - nrfx_saadc_buffer_set(samples_buffer, active_channel_cnt); + nrfx_err = nrfx_saadc_buffer_set( + samples_buffer, + (m_data.internal_timer_enabled + ? (active_channel_cnt * (1 + sequence->options->extra_samplings)) + : active_channel_cnt)); + if (nrfx_err != NRFX_SUCCESS) { + LOG_ERR("Failed to set buffer: %08x", nrfx_err); + return -EINVAL; + } adc_context_start_read(&m_data.ctx, sequence); @@ -633,11 +737,15 @@ static void event_handler(const nrfx_saadc_evt_t *event) if (event->type == NRFX_SAADC_EVT_DONE) { dmm_buffer_in_release( m_data.mem_reg, m_data.user_buffer, - NRFX_SAADC_SAMPLES_TO_BYTES(m_data.active_channel_cnt), + (m_data.internal_timer_enabled + ? (NRFX_SAADC_SAMPLES_TO_BYTES(m_data.active_channel_cnt) * + (1 + m_data.ctx.sequence.options->extra_samplings)) + : NRFX_SAADC_SAMPLES_TO_BYTES(m_data.active_channel_cnt)), event->data.done.p_buffer); if (has_single_ended(&m_data.ctx.sequence)) { - correct_single_ended(&m_data.ctx.sequence, m_data.user_buffer); + correct_single_ended(&m_data.ctx.sequence, m_data.user_buffer, + event->data.done.size); } adc_context_on_sampling_done(&m_data.ctx, DEVICE_DT_INST_GET(0)); } else if (event->type == NRFX_SAADC_EVT_CALIBRATEDONE) { @@ -646,6 +754,8 @@ static void event_handler(const nrfx_saadc_evt_t *event) LOG_ERR("Cannot start sampling: 0x%08x", err); adc_context_complete(&m_data.ctx, -EIO); } + } else if (event->type == NRFX_SAADC_EVT_FINISHED) { + adc_context_complete(&m_data.ctx, 0); } } @@ -653,6 +763,8 @@ static int init_saadc(const struct device *dev) { nrfx_err_t err; + k_timer_init(&m_data.timer, adc_context_on_timer_expired, NULL); + /* The priority value passed here is ignored (see nrfx_glue.h). */ err = nrfx_saadc_init(0); if (err != NRFX_SUCCESS) {