From 3e8879bfac576666146cffdee73005222bf655fb Mon Sep 17 00:00:00 2001 From: Jakub Zymelka Date: Tue, 5 Aug 2025 09:13:03 +0200 Subject: [PATCH 1/3] [nrf fromlist] drivers: adc: nrfx_saadc: Add support for DMM Add support for DMM which manages cache and dedicated memory spaces. Upstream PR #: 90751 Signed-off-by: Jakub Zymelka (cherry picked from commit 05746fdc63754ddf8eee3ce25485760dbcb86df4) --- drivers/adc/adc_nrfx_saadc.c | 72 +++++++++++++++++------------------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/drivers/adc/adc_nrfx_saadc.c b/drivers/adc/adc_nrfx_saadc.c index 2ba7b6efd00..7d442b482e6 100644 --- a/drivers/adc/adc_nrfx_saadc.c +++ b/drivers/adc/adc_nrfx_saadc.c @@ -13,6 +13,7 @@ #include #include #include +#include LOG_MODULE_REGISTER(adc_nrfx_saadc, CONFIG_ADC_LOG_LEVEL); @@ -97,34 +98,19 @@ BUILD_ASSERT((NRF_SAADC_AIN0 == NRF_SAADC_INPUT_AIN0) && "Definitions from nrf-adc.h do not match those from nrf_saadc.h"); #endif -#if defined(CONFIG_NRF_PLATFORM_HALTIUM) -#include -/* Haltium devices always use bounce buffers in RAM */ -static uint16_t adc_samples_buffer[SAADC_CH_NUM] DMM_MEMORY_SECTION(DT_NODELABEL(adc)); - -#define ADC_BUFFER_IN_RAM - -#endif /* defined(CONFIG_NRF_PLATFORM_HALTIUM) */ - struct driver_data { struct adc_context ctx; uint8_t single_ended_channels; - nrf_saadc_value_t *buffer; /* Pointer to the buffer with converted samples. */ uint8_t active_channel_cnt; - -#if defined(ADC_BUFFER_IN_RAM) - void *samples_buffer; + void *mem_reg; void *user_buffer; -#endif }; 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), -#if defined(ADC_BUFFER_IN_RAM) - .samples_buffer = adc_samples_buffer, -#endif + .mem_reg = DMM_DEV_TO_REG(DT_NODELABEL(adc)), }; /* Forward declaration */ @@ -392,22 +378,29 @@ static void adc_context_start_sampling(struct adc_context *ctx) if (ret != NRFX_SUCCESS) { LOG_ERR("Cannot start sampling: 0x%08x", ret); - adc_context_complete(&m_data.ctx, -EIO); + adc_context_complete(ctx, -EIO); } } } static void adc_context_update_buffer_pointer(struct adc_context *ctx, bool repeat) { + void *samples_buffer; + if (!repeat) { -#if defined(ADC_BUFFER_IN_RAM) m_data.user_buffer = (uint16_t *)m_data.user_buffer + m_data.active_channel_cnt; -#else - nrf_saadc_value_t *buffer = (uint16_t *)m_data.buffer + m_data.active_channel_cnt; + } - nrfx_saadc_buffer_set(buffer, m_data.active_channel_cnt); -#endif + 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); } + + nrfx_saadc_buffer_set(samples_buffer, m_data.active_channel_cnt); } static int get_resolution(const struct adc_sequence *sequence, nrf_saadc_resolution_t *resolution) @@ -502,12 +495,12 @@ 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) +static void correct_single_ended(const struct adc_sequence *sequence, nrf_saadc_value_t *buffer) { 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 *)m_data.buffer; + int16_t *sample = (int16_t *)buffer; while (channel_bit <= single_ended_channels) { if (channel_bit & selected_channels) { @@ -532,6 +525,7 @@ static int start_read(const struct device *dev, nrf_saadc_oversample_t oversampling; uint8_t active_channel_cnt = 0U; uint8_t channel_id = 0U; + void *samples_buffer; /* Signal an error if channel selection is invalid (no channels or * a non-existing one is selected). @@ -582,16 +576,21 @@ static int start_read(const struct device *dev, } m_data.active_channel_cnt = active_channel_cnt; -#if defined(ADC_BUFFER_IN_RAM) m_data.user_buffer = sequence->buffer; - nrfx_saadc_buffer_set(m_data.samples_buffer, active_channel_cnt); -#else + error = dmm_buffer_in_prepare(m_data.mem_reg, + m_data.user_buffer, + NRFX_SAADC_SAMPLES_TO_BYTES(active_channel_cnt), + &samples_buffer); + if (error != 0) { + LOG_ERR("DMM buffer allocation failed err=%d", error); + return error; + } + /* 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(sequence->buffer, active_channel_cnt); -#endif + nrfx_saadc_buffer_set(samples_buffer, active_channel_cnt); adc_context_start_read(&m_data.ctx, sequence); @@ -632,17 +631,14 @@ static void event_handler(const nrfx_saadc_evt_t *event) nrfx_err_t err; if (event->type == NRFX_SAADC_EVT_DONE) { - m_data.buffer = event->data.done.p_buffer; + dmm_buffer_in_release( + m_data.mem_reg, m_data.user_buffer, + 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); + correct_single_ended(&m_data.ctx.sequence, m_data.user_buffer); } - -#if defined(ADC_BUFFER_IN_RAM) - memcpy(m_data.user_buffer, m_data.samples_buffer, - NRFX_SAADC_SAMPLES_TO_BYTES(m_data.active_channel_cnt)); -#endif - adc_context_on_sampling_done(&m_data.ctx, DEVICE_DT_INST_GET(0)); } else if (event->type == NRFX_SAADC_EVT_CALIBRATEDONE) { err = nrfx_saadc_mode_trigger(); From 88ec19da69e6b9583448b5c3e70c832b3d7c1bfe Mon Sep 17 00:00:00 2001 From: Jakub Zymelka Date: Tue, 5 Aug 2025 13:55:10 +0200 Subject: [PATCH 2/3] [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 (cherry picked from commit 77547f89162265a6a4a285175b8e60b1dac7ae04) --- 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) { From cb77d622153191a49d72f3d5495827c335dfa35e Mon Sep 17 00:00:00 2001 From: Jakub Zymelka Date: Tue, 5 Aug 2025 14:00:57 +0200 Subject: [PATCH 3/3] [nrf fromlist] drivers: adc: nrfx: enable negative values for single-ended ADC readings The ADC driver API already supports ADC readings which can return signed values, these are differential readings. In Nordic's datasheet, we have a mode called "single ended", but its just a name. "Single ended" is a differential reading, with the negative channel tied to GND. This is not compatible with zephyrs definition of a single ended reading. To support Nordic's "single ended" mode, the user must configure a differential reading, with the negative input tied to ground, which the saadc driver can then use to configure the reading as Nordic SAADC "single ended", and return negative values as expected. Upstream PR #: 94069 Signed-off-by: Jakub Zymelka (cherry picked from commit 84ab620777ceec9b286f3fe6640ed0869c682c0b) --- drivers/adc/adc_nrfx_saadc.c | 20 ++++++++++++------ include/zephyr/dt-bindings/adc/nrf-saadc.h | 24 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/drivers/adc/adc_nrfx_saadc.c b/drivers/adc/adc_nrfx_saadc.c index 94af0cddac3..9b0f59084b6 100644 --- a/drivers/adc/adc_nrfx_saadc.c +++ b/drivers/adc/adc_nrfx_saadc.c @@ -191,7 +191,6 @@ static int input_assign(nrf_saadc_input_t *pin_p, if (channel_cfg->differential) { if (channel_cfg->input_negative > ARRAY_SIZE(saadc_psels) || - channel_cfg->input_negative < NRF_SAADC_AIN0 || (IS_ENABLED(CONFIG_NRF_PLATFORM_HALTIUM) && (channel_cfg->input_positive > NRF_SAADC_AIN7) != (channel_cfg->input_negative > NRF_SAADC_AIN7))) { @@ -199,14 +198,17 @@ static int input_assign(nrf_saadc_input_t *pin_p, channel_cfg->input_negative); return -EINVAL; } - *pin_n = saadc_psels[channel_cfg->input_negative]; + *pin_n = channel_cfg->input_negative == NRF_SAADC_GND ? + NRF_SAADC_INPUT_DISABLED : + saadc_psels[channel_cfg->input_negative]; } else { *pin_n = NRF_SAADC_INPUT_DISABLED; } #else *pin_p = channel_cfg->input_positive; - *pin_n = channel_cfg->differential ? channel_cfg->input_negative - : NRF_SAADC_INPUT_DISABLED; + *pin_n = (channel_cfg->differential && (channel_cfg->input_negative != NRF_SAADC_GND)) + ? channel_cfg->input_negative + : NRF_SAADC_INPUT_DISABLED; #endif LOG_DBG("ADC positive input: %d", *pin_p); LOG_DBG("ADC negative input: %d", *pin_n); @@ -356,8 +358,14 @@ static int adc_nrfx_channel_setup(const struct device *dev, * after ADC sequence ends. */ if (channel_cfg->differential) { - ch_cfg->mode = NRF_SAADC_MODE_DIFFERENTIAL; - m_data.single_ended_channels &= ~BIT(channel_cfg->channel_id); + if (channel_cfg->input_negative == NRF_SAADC_GND) { + ch_cfg->mode = NRF_SAADC_MODE_SINGLE_ENDED; + /* Do not mark as single-ended to not correct negative values. */ + m_data.single_ended_channels &= ~BIT(channel_cfg->channel_id); + } else { + ch_cfg->mode = NRF_SAADC_MODE_DIFFERENTIAL; + m_data.single_ended_channels &= ~BIT(channel_cfg->channel_id); + } } else { ch_cfg->mode = NRF_SAADC_MODE_SINGLE_ENDED; m_data.single_ended_channels |= BIT(channel_cfg->channel_id); diff --git a/include/zephyr/dt-bindings/adc/nrf-saadc.h b/include/zephyr/dt-bindings/adc/nrf-saadc.h index 4a3deb95cff..e5a86150cd4 100644 --- a/include/zephyr/dt-bindings/adc/nrf-saadc.h +++ b/include/zephyr/dt-bindings/adc/nrf-saadc.h @@ -7,6 +7,30 @@ #ifndef ZEPHYR_INCLUDE_DT_BINDINGS_ADC_NRF_SAADC_H_ #define ZEPHYR_INCLUDE_DT_BINDINGS_ADC_NRF_SAADC_H_ +/** + * @brief Short ADC negative input to ground + * + * @details The nRF SAADC hardware only supports differential readings. + * The nRF SAADC SE (single ended) mode is differential with the negative + * input shorted to GND (ground). To use the nRF SAADC SE mode, set the + * negative input to NRF_SAADC_GND: + * + * @code{.dts} + * zephyr,input-positive = ; + * zephyr,input-negative = ; + * @endcode + * + * The nRF SAADC driver also supports using the nRF SAADC SE mode in + * emulated "single-ended" mode, as defined by zephyr. In this mode, + * negative readings will be clamped to 0 by software to emulate the + * behavior of an ADC in "single-ended" mode, as defined by zephyr. To + * do this, only define the positive input: + * + * @code{.dts} + * zephyr,input-positive = ; + * @endcode + */ +#define NRF_SAADC_GND 0 #define NRF_SAADC_AIN0 1 #define NRF_SAADC_AIN1 2 #define NRF_SAADC_AIN2 3