From 0fb3e478ffb7db65bd1b44525d0ffc04003b9c5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Chru=C5=9Bci=C5=84ski?= Date: Thu, 11 Sep 2025 14:13:51 +0200 Subject: [PATCH 1/5] dts: vendor: nordic: Add mvdma nodes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add MVDMA binding and nodes to nrf54h20. Signed-off-by: Krzysztof Chruściński --- dts/bindings/dma/nordic,nrf-mvdma.yaml | 15 +++++++++++++++ dts/vendor/nordic/nrf54h20.dtsi | 14 ++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 dts/bindings/dma/nordic,nrf-mvdma.yaml diff --git a/dts/bindings/dma/nordic,nrf-mvdma.yaml b/dts/bindings/dma/nordic,nrf-mvdma.yaml new file mode 100644 index 0000000000000..22ab79d9ba5da --- /dev/null +++ b/dts/bindings/dma/nordic,nrf-mvdma.yaml @@ -0,0 +1,15 @@ +# Copyright (c) 2025, Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +description: Nordic MVDMA + +compatible: "nordic,nrf-mvdma" + +include: base.yaml + +properties: + reg: + required: true + + interrupts: + required: true diff --git a/dts/vendor/nordic/nrf54h20.dtsi b/dts/vendor/nordic/nrf54h20.dtsi index dfe0c4b7c6c86..76f2c454dd06c 100644 --- a/dts/vendor/nordic/nrf54h20.dtsi +++ b/dts/vendor/nordic/nrf54h20.dtsi @@ -316,6 +316,13 @@ reg = <0x52000000 0x1000000>; ranges = <0x0 0x52000000 0x1000000>; + cpuapp_mvdma: mvdma@3000 { + compatible = "nordic,nrf-mvdma"; + reg = <0x3000 0x1000>; + status = "okay"; + interrupts = <3 NRF_DEFAULT_IRQ_PRIORITY>; + }; + cpuapp_hsfll: clock@d000 { compatible = "nordic,nrf-iron-hsfll-local"; #clock-cells = <0>; @@ -372,6 +379,13 @@ #size-cells = <1>; ranges = <0x0 0x53000000 0x1000000>; + cpurad_mvdma: mvdma@3000 { + compatible = "nordic,nrf-mvdma"; + reg = <0x3000 0x1000>; + status = "okay"; + interrupts = <3 NRF_DEFAULT_IRQ_PRIORITY>; + }; + cpurad_hsfll: clock@d000 { compatible = "nordic,nrf-hsfll-local"; #clock-cells = <0>; From e3e2b74ebf2428c7dfc98375bcb33c76a019f530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Chru=C5=9Bci=C5=84ski?= Date: Thu, 2 Oct 2025 11:56:56 +0200 Subject: [PATCH 2/5] pm: policy: Add option to lock all power states MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add function for getting and putting a lock for all power states. It is much faster version of requesting 0 us latency with actual intention to disable all power states. Signed-off-by: Krzysztof Chruściński --- include/zephyr/pm/policy.h | 22 ++++++++++++++++++++++ subsys/pm/policy/policy_state_lock.c | 26 +++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/include/zephyr/pm/policy.h b/include/zephyr/pm/policy.h index b38fa37f254b0..0be63d38f8ce9 100644 --- a/include/zephyr/pm/policy.h +++ b/include/zephyr/pm/policy.h @@ -112,6 +112,7 @@ const struct pm_state_info *pm_policy_next_state(uint8_t cpu, int32_t ticks); * * @see pm_policy_state_lock_put() */ + void pm_policy_state_lock_get(enum pm_state state, uint8_t substate_id); /** @@ -125,6 +126,18 @@ void pm_policy_state_lock_get(enum pm_state state, uint8_t substate_id); */ void pm_policy_state_lock_put(enum pm_state state, uint8_t substate_id); +/** + * @brief Request to lock all power states. + * + * Requests use a reference counter. + */ +void pm_policy_state_all_lock_get(void); + +/** + * @brief Release locking of all power states. + */ +void pm_policy_state_all_lock_put(void); + /** * @brief Apply power state constraints by locking the specified states. * @@ -274,6 +287,14 @@ static inline void pm_policy_state_lock_put(enum pm_state state, uint8_t substat ARG_UNUSED(substate_id); } +static inline void pm_policy_state_all_lock_get(void) +{ +} + +static inline void pm_policy_state_all_lock_put(void) +{ +} + static inline bool pm_policy_state_lock_is_active(enum pm_state state, uint8_t substate_id) { ARG_UNUSED(state); @@ -344,6 +365,7 @@ static inline void pm_policy_device_power_lock_put(const struct device *dev) { ARG_UNUSED(dev); } + #endif /* CONFIG_PM_POLICY_DEVICE_CONSTRAINTS */ #if defined(CONFIG_PM) || defined(CONFIG_PM_POLICY_LATENCY_STANDALONE) || defined(__DOXYGEN__) diff --git a/subsys/pm/policy/policy_state_lock.c b/subsys/pm/policy/policy_state_lock.c index b4cc319339baa..5fc305122ddfb 100644 --- a/subsys/pm/policy/policy_state_lock.c +++ b/subsys/pm/policy/policy_state_lock.c @@ -43,10 +43,27 @@ static const struct { static atomic_t lock_cnt[ARRAY_SIZE(substates)]; static atomic_t latency_mask = BIT_MASK(ARRAY_SIZE(substates)); static atomic_t unlock_mask = BIT_MASK(ARRAY_SIZE(substates)); +static atomic_t global_lock_cnt; static struct k_spinlock lock; #endif +void pm_policy_state_all_lock_get(void) +{ +#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_power_state) + (void)atomic_inc(&global_lock_cnt); +#endif +} + +void pm_policy_state_all_lock_put(void) +{ +#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_power_state) + __ASSERT(global_lock_cnt > 0, "Unbalanced state lock get/put"); + (void)atomic_dec(&global_lock_cnt); +#endif +} + + void pm_policy_state_lock_get(enum pm_state state, uint8_t substate_id) { #if DT_HAS_COMPAT_STATUS_OKAY(zephyr_power_state) @@ -106,7 +123,8 @@ bool pm_policy_state_lock_is_active(enum pm_state state, uint8_t substate_id) for (size_t i = 0; i < ARRAY_SIZE(substates); i++) { if (substates[i].state == state && (substates[i].substate_id == substate_id || substate_id == PM_ALL_SUBSTATES)) { - return atomic_get(&lock_cnt[i]) != 0; + return (atomic_get(&lock_cnt[i]) != 0) || + (atomic_get(&global_lock_cnt) != 0); } } #endif @@ -121,7 +139,8 @@ bool pm_policy_state_is_available(enum pm_state state, uint8_t substate_id) if (substates[i].state == state && (substates[i].substate_id == substate_id || substate_id == PM_ALL_SUBSTATES)) { return (atomic_get(&lock_cnt[i]) == 0) && - (atomic_get(&latency_mask) & BIT(i)); + (atomic_get(&latency_mask) & BIT(i)) && + (atomic_get(&global_lock_cnt) == 0); } } #endif @@ -135,7 +154,8 @@ bool pm_policy_state_any_active(void) /* Check if there is any power state that is not locked and not disabled due * to latency requirements. */ - return atomic_get(&unlock_mask) & atomic_get(&latency_mask); + return atomic_get(&unlock_mask) & atomic_get(&latency_mask) && + (atomic_get(&global_lock_cnt) == 0); #endif return true; } From 64b0de059af390e235d119e4c8ff2d3ca0cdea85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Chru=C5=9Bci=C5=84ski?= Date: Thu, 11 Sep 2025 13:42:14 +0200 Subject: [PATCH 3/5] soc: nordic: common: Add driver for Nordic MVDMA peripheral MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MVDMA driver does not implement Zephyr DMA API. It implements its own due to performance reasons and it is intended to be used mainly in the Nordic specific code. Signed-off-by: Krzysztof Chruściński --- soc/nordic/common/CMakeLists.txt | 1 + soc/nordic/common/Kconfig | 5 + soc/nordic/common/mvdma.c | 206 +++++++++++++++++++ soc/nordic/common/mvdma.h | 339 +++++++++++++++++++++++++++++++ soc/nordic/nrf54h/soc.c | 6 + 5 files changed, 557 insertions(+) create mode 100644 soc/nordic/common/mvdma.c create mode 100644 soc/nordic/common/mvdma.h diff --git a/soc/nordic/common/CMakeLists.txt b/soc/nordic/common/CMakeLists.txt index 04f0c1a3219c4..a32c11d655566 100644 --- a/soc/nordic/common/CMakeLists.txt +++ b/soc/nordic/common/CMakeLists.txt @@ -40,3 +40,4 @@ endif() zephyr_library_sources_ifdef(CONFIG_NRF_SYS_EVENT nrf_sys_event.c) zephyr_library_sources_ifdef(CONFIG_MRAM_LATENCY mram_latency.c) +zephyr_library_sources_ifdef(CONFIG_MVDMA mvdma.c) diff --git a/soc/nordic/common/Kconfig b/soc/nordic/common/Kconfig index e1fcd713c77ce..053b1c156de12 100644 --- a/soc/nordic/common/Kconfig +++ b/soc/nordic/common/Kconfig @@ -48,6 +48,11 @@ source "subsys/logging/Kconfig.template.log_config" endif # MRAM_LATENCY +config MVDMA + depends on DT_HAS_NORDIC_NRF_MVDMA_ENABLED + default y + bool "nRF MVDMA driver" + if HAS_NORDIC_DMM config DMM_HEAP_CHUNKS diff --git a/soc/nordic/common/mvdma.c b/soc/nordic/common/mvdma.c new file mode 100644 index 0000000000000..7a1ee4974054e --- /dev/null +++ b/soc/nordic/common/mvdma.c @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +/* To be removed when nrfx comes with those symbols. */ +#define NRF_MVDMA_INT_COMPLETED0_MASK MVDMA_INTENSET_COMPLETED0_Msk +#define NRF_MVDMA_EVENT_COMPLETED0 offsetof(NRF_MVDMA_Type, EVENTS_COMPLETED[0]) + +#define MVDMA_DO_COUNT(node) 1 + + +BUILD_ASSERT((DT_FOREACH_STATUS_OKAY(nordic_nrf_mvdma, MVDMA_DO_COUNT) 0) == 1); + +#define MVDMA_NODE() DT_COMPAT_GET_ANY_STATUS_OKAY(nordic_nrf_mvdma) + +static sys_slist_t list; +static atomic_t hw_err; +static struct mvdma_ctrl *curr_ctrl; +static NRF_MVDMA_Type *reg = (NRF_MVDMA_Type *)DT_REG_ADDR(MVDMA_NODE()); + +static uint32_t dummy_jobs[] __aligned(CONFIG_DCACHE_LINE_SIZE) = { + NRF_MVDMA_JOB_TERMINATE, + NRF_MVDMA_JOB_TERMINATE, +}; + +static void xfer_start(uint32_t src, uint32_t sink) +{ + nrf_mvdma_event_clear(reg, NRF_MVDMA_EVENT_COMPLETED0); + nrf_mvdma_source_list_ptr_set(reg, (void *)src); + nrf_mvdma_sink_list_ptr_set(reg, (void *)sink); + nrf_mvdma_task_trigger(reg, NRF_MVDMA_TASK_START0); +} + +static int xfer(struct mvdma_ctrl *ctrl, uint32_t src, uint32_t sink, bool queue) +{ + int rv, key; + bool int_en = true; + + key = irq_lock(); + if (nrf_mvdma_activity_check(reg) || (curr_ctrl && curr_ctrl->handler)) { + if (queue) { + ctrl->source = src; + ctrl->sink = sink; + sys_slist_append(&list, &ctrl->node); + rv = 1; + } else { + irq_unlock(key); + return -EBUSY; + } + } else { + /* There might be some pending request that need to be marked as finished. */ + if (curr_ctrl != NULL) { + sys_snode_t *node; + struct mvdma_ctrl *prev; + + curr_ctrl->handler = (mvdma_handler_t)1; + while ((node = sys_slist_get(&list)) != NULL) { + prev = CONTAINER_OF(node, struct mvdma_ctrl, node); + prev->handler = (mvdma_handler_t)1; + } + } + + curr_ctrl = ctrl; + xfer_start(src, sink); + if (ctrl->handler == NULL) { + int_en = false; + } + rv = 0; + } + irq_unlock(key); + + if (int_en) { + nrf_mvdma_int_enable(reg, NRF_MVDMA_INT_COMPLETED0_MASK); + } + + pm_policy_state_all_lock_get(); + + return rv; +} + +int mvdma_xfer(struct mvdma_ctrl *ctrl, struct mvdma_jobs_desc *desc, bool queue) +{ + sys_cache_data_flush_range(desc->source, desc->source_desc_size); + sys_cache_data_flush_range(desc->sink, desc->sink_desc_size); + return xfer(ctrl, (uint32_t)desc->source, (uint32_t)desc->sink, queue); +} + +int mvdma_basic_xfer(struct mvdma_ctrl *ctrl, struct mvdma_basic_desc *desc, bool queue) +{ + sys_cache_data_flush_range(desc, sizeof(*desc)); + return xfer(ctrl, (uint32_t)&desc->source, (uint32_t)&desc->sink, queue); +} + +int mvdma_xfer_check(const struct mvdma_ctrl *ctrl) +{ + if (hw_err != NRF_MVDMA_ERR_NO_ERROR) { + return -EIO; + } + + if (nrf_mvdma_event_check(reg, NRF_MVDMA_EVENT_COMPLETED0)) { + curr_ctrl = NULL; + } else if (ctrl->handler == NULL) { + return -EBUSY; + } + + pm_policy_state_all_lock_put(); + + return 0; +} + +enum mvdma_err mvdma_error_check(void) +{ + return atomic_set(&hw_err, 0); +} + +static void error_handler(void) +{ + if (nrf_mvdma_event_check(reg, NRF_MVDMA_EVENT_SOURCEBUSERROR)) { + nrf_mvdma_event_clear(reg, NRF_MVDMA_EVENT_SOURCEBUSERROR); + hw_err = NRF_MVDMA_ERR_SOURCE; + } + + if (nrf_mvdma_event_check(reg, NRF_MVDMA_EVENT_SINKBUSERROR)) { + nrf_mvdma_event_clear(reg, NRF_MVDMA_EVENT_SINKBUSERROR); + hw_err = NRF_MVDMA_ERR_SINK; + } +} + +static void ch_handler(int status) +{ + int key; + struct mvdma_ctrl *ctrl = curr_ctrl; + sys_snode_t *node; + bool int_dis = true; + + key = irq_lock(); + node = sys_slist_get(&list); + if (node) { + struct mvdma_ctrl *next = CONTAINER_OF(node, struct mvdma_ctrl, node); + + curr_ctrl = next; + xfer_start((uint32_t)next->source, (uint32_t)next->sink); + if (next->handler || !sys_slist_is_empty(&list)) { + int_dis = false; + } + } else { + curr_ctrl = NULL; + } + if (int_dis) { + nrf_mvdma_int_disable(reg, NRF_MVDMA_INT_COMPLETED0_MASK); + } + irq_unlock(key); + + if (ctrl->handler) { + pm_policy_state_all_lock_put(); + ctrl->handler(ctrl->user_data, hw_err == NRF_MVDMA_ERR_NO_ERROR ? 0 : -EIO); + } else { + /* Set handler variable to non-null to indicated that transfer has finished. */ + ctrl->handler = (mvdma_handler_t)1; + } +} + +static void mvdma_isr(const void *arg) +{ + uint32_t ints = nrf_mvdma_int_pending_get(reg); + + if (ints & NRF_MVDMA_INT_COMPLETED0_MASK) { + ch_handler(0); + } else { + error_handler(); + } +} + +void mvdma_resume(void) +{ + /* bus errors. */ + nrf_mvdma_int_enable(reg, NRF_MVDMA_INT_SOURCEBUSERROR_MASK | + NRF_MVDMA_INT_SINKBUSERROR_MASK); + + /* Dummy transfer to get COMPLETED event set. */ + nrf_mvdma_source_list_ptr_set(reg, (void *)&dummy_jobs[0]); + nrf_mvdma_sink_list_ptr_set(reg, (void *)&dummy_jobs[1]); + nrf_mvdma_task_trigger(reg, NRF_MVDMA_TASK_START0); +} + +int mvdma_init(void) +{ + sys_cache_data_flush_range(dummy_jobs, sizeof(dummy_jobs)); + + sys_slist_init(&list); + + IRQ_CONNECT(DT_IRQN(MVDMA_NODE()), DT_IRQ(MVDMA_NODE(), priority), mvdma_isr, 0, 0); + irq_enable(DT_IRQN(MVDMA_NODE())); + + mvdma_resume(); + + return 0; +} diff --git a/soc/nordic/common/mvdma.h b/soc/nordic/common/mvdma.h new file mode 100644 index 0000000000000..ed28b38b71607 --- /dev/null +++ b/soc/nordic/common/mvdma.h @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * nRF SoC specific public APIs for MVDMA peripheral. + */ + +#ifndef SOC_NORDIC_COMMON_MVDMA_H_ +#define SOC_NORDIC_COMMON_MVDMA_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** MVDMA Attribute field offset. */ +#define NRF_MVDMA_ATTR_OFF 24 + +/** MVDMA Extended attribute field offset. */ +#define NRF_MVDMA_EXT_ATTR_OFF 30 + +/** + * @anchor NRF_MVDMA_ATTR + * @name MVDMA job attributes. + * @{ + */ + +/** @brief Default transfer. */ +#define NRF_MVDMA_ATTR_DEFAULT 7 + +/** @brief Byte swapping to change the endianness. + * + * When set, endianness on the data pointed to by memory address will be swapped. + */ +#define NRF_MVDMA_ATTR_BYTE_SWAP 0 + +/** @brief Chaining job lists. + * + * When set, pointer in the descriptor is treated as a pointer to the next job description. + */ +#define NRF_MVDMA_ATTR_JOB_LINK 1 + +/** @brief Fill sink address with zeroes. + * + * The source list must contain a single null job. This attribute is the exception to the rule + * that the total number of bytes in the source and sink list must match. + */ +#define NRF_MVDMA_ATTR_ZERO_FILL 2 + +/** @brief Fixed attributes for all jobs in the descriptor. + * + * It can be used to compress the descriptor. With this attribute, only one attribute/size + * word is read with the first job and the same information is used for the rest of the jobs + * that only have addresses. + */ +#define NRF_MVDMA_ATTR_FIXED_PARAMS 3 + +/** @brief Fixed address. + * + * This mode can be used to read multiple entries from a peripheral where the output changes + * every read, for example a TRNG or FIFO. + */ +#define NRF_MVDMA_ATTR_STATIC_ADDR 4 + +/** @brief Optimization for many short bursts. + * + * This attribute can be used to transfer data using bufferable write accesses. MVDMA may receive a + * write response before the data reaches the destination and starts fetching the next job. This can + * provide better performance especially when there are many shorts bursts being sent or many small + * jobs with different user-defined attributes. + */ +#define NRF_MVDMA_ATTR_SHORT_BURSTS 5 + +/** @brief Calculate CRC + * + * This attribute causes a CRC checksum to be calculated for the complete source job list. + * When used, all jobs in the joblist must have this attribute. + */ +#define NRF_MVDMA_ATTR_CRC 6 + +/**@} */ + +/** + * @anchor NRF_MVDMA_EXT_ATTR + * @name MVDMA extended job attributes. + * @{ + */ +/** @brief Job is accessing a peripheral register. */ +#define NRF_MVDMA_EXT_ATTR_PERIPH 1 + +/** @brief Enable event on job completion. + * + * If this bit is set, an event will be generated when this job descriptor has been processed. + * An interrupt will be triggered if enabled for that event. + */ +#define NRF_MVDMA_EXT_ATTR_INT 2 + +/**@} */ + + +/** @brief Signature of a transfer completion handler. + * + * @brief user_data User data. + * @brief status Operation status. + */ +typedef void (*mvdma_handler_t)(void *user_data, int status); + +/** @brief Helper macro for building a 32 bit word for job descriptor with attributes and length. + * + * @param _size Transfer length in the job. + * @param _attr Job attribute. See @ref NRF_MVDMA_ATTR. + * @param _ext_attr Extended job attribute. See @ref NRF_MVDMA_EXT_ATTR. + */ +#define NRF_MVDMA_JOB_ATTR(_size, _attr, _ext_attr) \ + (((_size) & 0xFFFFFF) | ((_attr) << NRF_MVDMA_ATTR_OFF) | \ + ((_ext_attr) << NRF_MVDMA_EXT_ATTR_OFF)) + +/** @brief Helper macro for building a 2 word job descriptor. + * + * @param _ptr Address. + * @param _size Transfer length in the job. + * @param _attr Job attribute. See @ref NRF_MVDMA_ATTR. + * @param _ext_attr Extended job attribute. See @ref NRF_MVDMA_EXT_ATTR. + */ +#define NRF_MVDMA_JOB_DESC(_ptr, _size, _attr, _ext_attr) \ + (uint32_t)(_ptr), NRF_MVDMA_JOB_ATTR(_size, _attr, _ext_attr) + +/** @brief Helper macro for creating a termination entry in the array with job descriptors. */ +#define NRF_MVDMA_JOB_TERMINATE 0, 0 + +/** @brief A structure used for a basic transfer that has a single job in source and sink. + * + * @note It must be aligned to a data cache line! + */ +struct mvdma_basic_desc { + uint32_t source; + uint32_t source_attr_len; + uint32_t source_terminate; + uint32_t source_padding; + uint32_t sink; + uint32_t sink_attr_len; + uint32_t sink_terminate; + uint32_t sink_padding; +}; + +BUILD_ASSERT(COND_CODE_1(CONFIG_DCACHE, + (sizeof(struct mvdma_basic_desc) == CONFIG_DCACHE_LINE_SIZE), (1))); + +/** @brief A structure that holds the information about job descriptors. + * + * Job descriptors must be data cache line aligned and padded. + */ +struct mvdma_jobs_desc { + /** Pointer to an array with source job descriptors terminated with 2 null words. */ + uint32_t *source; + + /** Size in bytes of an array with source job descriptors (including 2 null words). */ + size_t source_desc_size; + + /** Pointer to an array with sink job descriptors terminated with 2 null words. */ + uint32_t *sink; + + /** Size in bytes of an array with sink job descriptors (including 2 null words). */ + size_t sink_desc_size; +}; + +enum mvdma_err { + NRF_MVDMA_ERR_NO_ERROR, + NRF_MVDMA_ERR_SOURCE, + NRF_MVDMA_ERR_SINK, +}; + +/** @brief A helper macro for initializing a basic descriptor. + * + * @param _src Source address. + * @param _src_len Source length. + * @param _src_attr Source attributes. + * @param _src_ext_attr Source extended attributes. + * @param _sink Source address. + * @param _sink_len Source length. + * @param _sink_attr Source attributes. + * @param _sink_ext_attr Source extended attributes. + */ +#define NRF_MVDMA_BASIC_INIT(_src, _src_len, _src_attr, _src_ext_attr, \ + _sink, _sink_len, _sink_attr, _sink_ext_attr) \ +{ \ + .source = (uint32_t)(_src), \ + .source_attr_len = NRF_MVDMA_JOB_ATTR((_src_len), _src_attr, _src_ext_attr), \ + .source_terminate = 0, \ + .source_padding = 0, \ + .sink = (uint32_t)(_sink), \ + .sink_attr_len = NRF_MVDMA_JOB_ATTR((_sink_len), _sink_attr, _sink_ext_attr), \ + .sink_terminate = 0, \ + .sink_padding = 0, \ +} + +/** @brief A helper macro for initializing a basic descriptor to perform a memcpy. + * + * @param _dst Destination address. + * @param _src Source address. + * @param _len Length. + */ +#define NRF_MVDMA_BASIC_MEMCPY_INIT(_dst, _src, _len) \ + NRF_MVDMA_BASIC_INIT(_src, _len, NRF_MVDMA_ATTR_DEFAULT, 0, \ + _dst, _len, NRF_MVDMA_ATTR_DEFAULT, 0) + +/** @brief A helper macro for initializing a basic descriptor to fill a buffer with 0's. + * + * @param _ptr Buffer address. + * @param _len Length. + */ +#define NRF_MVDMA_BASIC_ZERO_INIT(_ptr, _len) \ + NRF_MVDMA_BASIC_INIT(NULL, 0, 0, 0, _ptr, _len, NRF_MVDMA_ATTR_ZERO_FILL, 0) + +/** @brief Control structure used for the MVDMA transfer. */ +struct mvdma_ctrl { + /** Internally used. */ + sys_snode_t node; + + /** User handler. Free running mode if null. */ + mvdma_handler_t handler; + + /** User data passed to the handler. */ + void *user_data; + + uint32_t source; + + uint32_t sink; +}; + +/** @brief A helper macro for initializing a control structure. + * + * @param _handler User handler. If null transfer will be free running. + * @param _user_data User data passed to the handler. + */ +#define NRF_MVDMA_CTRL_INIT(_handler, _user_data) { \ + .handler = _handler, \ + .user_data = _user_data \ +} + +/** @brief Perform a basic transfer. + * + * Basic transfer allows for a single source and sink job descriptor. Since those descriptors + * fit into a single data cache line setup is a bit simple and faster than using a generic + * approach. The driver is handling cache flushing on that descriptor. + * + * Completion of the transfer can be reported with the handler called from MVDMA interrupt + * handler. Alternatively, transfer can be free running and @ref mvdma_xfer_check is + * used to poll for transfer completion. When free running mode is used then it is mandatory + * to poll until @ref mvdma_xfer_check reports transfer completion due to the power + * management. + * + * @note @p ctrl structure is owned by the driver during the transfer and must persist during + * that time and cannot be modified by the user until transfer is finished. + * + * @param[in,out] ctrl Control structure. Use null handler for free running mode. + * @param[in] desc Transfer descriptor. Must be data cache line aligned. + * @param[in] queue If true transfer will be queued if MVDMA is busy. If false then transfer + * will not be started. + * + * retval 0 transfer is successfully started. MVDMA was idle. + * retval 1 transfer is queued and will start when MVDMA is ready. MVDMA was busy. + * retval -EBUSY transfer is not started because MVDMA was busy and @p queue was false. + */ +int mvdma_basic_xfer(struct mvdma_ctrl *ctrl, struct mvdma_basic_desc *desc, + bool queue); + +/** @brief Perform a transfer. + * + * User must prepare job descriptors. The driver is handling cache flushing on that descriptors. + * + * Completion of the transfer can be reported with the handler called from the MVDMA interrupt + * handler. Alternatively, transfer can be free running and @ref mvdma_xfer_check is + * used to poll for transfer completion. When free running mode is used then it is mandatory + * to poll until @ref mvdma_xfer_check reports transfer completion due to the power + * management. + * + * @note Descriptors must not contain chaining (@ref NRF_MVDMA_ATTR_JOB_LINK) or + * compressed descriptors (@ref NRF_MVDMA_ATTR_FIXED_PARAMS). + * + * @note @p ctrl structure is owned by the driver during the transfer and must persist during + * that time and cannot be modified by the user until transfer is finished. + * + * @param[in,out] ctrl Control structure. Use null handler for free running mode. + * @param[in] desc Transfer descriptor. Must be data cache line aligned and padded. + * @param[in] queue If true transfer will be queued if MVDMA is busy. If false then transfer + * will not be started. + * + * retval 0 transfer is successfully started. MVDMA was idle. + * retval 1 transfer is queued and will start when MVDMA is ready. MVDMA was busy. + * retval -EBUSY transfer is not started because MVDMA was busy and @p queue was false. + */ +int mvdma_xfer(struct mvdma_ctrl *ctrl, struct mvdma_jobs_desc *desc, bool queue); + +/** @brief Poll for completion of free running transfer. + * + * It is mandatory to poll for the transfer until completion is reported. Not doing that may + * result in increased power consumption. + * + * @param ctrl Control structure used for the transfer. + * + * @retval 0 transfer is completed. MVDMA is idle. + * @retval 1 transfer is completed. MVDMA has started another transfer. + * @retval 2 transfer is completed. MVDMA continues with other transfer. + * @retval -EBUSY transfer is in progress. + * @retval -EIO MVDMA reported an error. + */ +int mvdma_xfer_check(const struct mvdma_ctrl *ctrl); + +/** @brief Read and clear error reported by the MDVMA. + * + * @return Enum with and error. + */ +enum mvdma_err mvdma_error_check(void); + +/** @brief Restore MVDMA configuration. + * + * Function must be called when system restores from the suspend to RAM sleep. + */ +void mvdma_resume(void); + +/** @brief Initialize MVDMA driver. + * + * retval 0 success. + */ +int mvdma_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* SOC_NORDIC_COMMON_MVDMA_H_ */ diff --git a/soc/nordic/nrf54h/soc.c b/soc/nordic/nrf54h/soc.c index 1206e6767aa28..70ed5572ad8ea 100644 --- a/soc/nordic/nrf54h/soc.c +++ b/soc/nordic/nrf54h/soc.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #if defined(CONFIG_SOC_NRF54H20_CPURAD_ENABLE) @@ -161,6 +162,11 @@ void soc_early_init_hook(void) err = dmm_init(); __ASSERT_NO_MSG(err == 0); + if (IS_ENABLED(CONFIG_MVDMA)) { + err = mvdma_init(); + __ASSERT_NO_MSG(err == 0); + } + #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(ccm030)) /* DMASEC is set to non-secure by default, which prevents CCM from * accessing secure memory. Change DMASEC to secure. From 6acecac030b742cd0d94c71e2cca4a434b92489d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Chru=C5=9Bci=C5=84ski?= Date: Thu, 11 Sep 2025 14:15:55 +0200 Subject: [PATCH 4/5] soc: nordic: nrf54h: Add MVDMA restore to S2RAM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MVDMA is present in the application domain so it's configuration need to be restored when waking up from suspend to RAM. Signed-off-by: Krzysztof Chruściński --- soc/nordic/nrf54h/pm_s2ram.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/soc/nordic/nrf54h/pm_s2ram.c b/soc/nordic/nrf54h/pm_s2ram.c index 753acdda68328..b74a71c354fa9 100644 --- a/soc/nordic/nrf54h/pm_s2ram.c +++ b/soc/nordic/nrf54h/pm_s2ram.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "pm_s2ram.h" #include "power.h" @@ -257,6 +258,9 @@ int soc_s2ram_suspend(pm_s2ram_system_off_fn_t system_off) #endif nvic_restore(&backup_data.nvic_context); scb_restore(&backup_data.scb_context); + if (IS_ENABLED(CONFIG_MVDMA)) { + mvdma_resume(); + } return ret; } From 11e1c3b0c64959a3bc57566a6cd8945fad5d1137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Chru=C5=9Bci=C5=84ski?= Date: Thu, 11 Sep 2025 13:53:03 +0200 Subject: [PATCH 5/5] tests: boards: nrf: Add test for MVDMA driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add test suite for MVDMA driver. Signed-off-by: Krzysztof Chruściński --- tests/boards/nrf/mvdma/CMakeLists.txt | 10 + tests/boards/nrf/mvdma/Kconfig | 15 + tests/boards/nrf/mvdma/README.txt | 3 + tests/boards/nrf/mvdma/prj.conf | 14 + tests/boards/nrf/mvdma/src/main.c | 818 ++++++++++++++++++++++++++ tests/boards/nrf/mvdma/testcase.yaml | 11 + 6 files changed, 871 insertions(+) create mode 100644 tests/boards/nrf/mvdma/CMakeLists.txt create mode 100644 tests/boards/nrf/mvdma/Kconfig create mode 100644 tests/boards/nrf/mvdma/README.txt create mode 100644 tests/boards/nrf/mvdma/prj.conf create mode 100644 tests/boards/nrf/mvdma/src/main.c create mode 100644 tests/boards/nrf/mvdma/testcase.yaml diff --git a/tests/boards/nrf/mvdma/CMakeLists.txt b/tests/boards/nrf/mvdma/CMakeLists.txt new file mode 100644 index 0000000000000..585ad7596b5e3 --- /dev/null +++ b/tests/boards/nrf/mvdma/CMakeLists.txt @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(nrf_mvdma_api) + +target_sources(app PRIVATE + src/main.c + ) diff --git a/tests/boards/nrf/mvdma/Kconfig b/tests/boards/nrf/mvdma/Kconfig new file mode 100644 index 0000000000000..d5d41cd19fd1e --- /dev/null +++ b/tests/boards/nrf/mvdma/Kconfig @@ -0,0 +1,15 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config STRESS_TEST_REPS + int "Number of loops in the stress test" + # For the simulated devices, which are run by default in CI, we set it to less to not spend too + # much CI time + default 500 if SOC_SERIES_BSIM_NRFXX + default 10000 + help + For how many loops will the stress test run. The higher this number the longer the + test and therefore the higher likelihood an unlikely race/event will be triggered. + +# Include Zephyr's Kconfig +source "Kconfig" diff --git a/tests/boards/nrf/mvdma/README.txt b/tests/boards/nrf/mvdma/README.txt new file mode 100644 index 0000000000000..8557edd5cf13e --- /dev/null +++ b/tests/boards/nrf/mvdma/README.txt @@ -0,0 +1,3 @@ +.. SPDX-License-Identifier: Apache-2.0 + +The purpose of this test is to validate custom nRF MVDMA driver. diff --git a/tests/boards/nrf/mvdma/prj.conf b/tests/boards/nrf/mvdma/prj.conf new file mode 100644 index 0000000000000..ad3864ea7026b --- /dev/null +++ b/tests/boards/nrf/mvdma/prj.conf @@ -0,0 +1,14 @@ +CONFIG_ZTEST=y +CONFIG_KERNEL_MEM_POOL=y +CONFIG_HEAP_MEM_POOL_SIZE=1024 + +# Float printing +CONFIG_REQUIRES_FLOAT_PRINTF=y +CONFIG_TEST_EXTRA_STACK_SIZE=2048 +CONFIG_MRAM_LATENCY=y +CONFIG_MRAM_LATENCY_AUTO_REQ=y + +CONFIG_ASSERT=n +CONFIG_SPIN_VALIDATE=n + +CONFIG_PM=y diff --git a/tests/boards/nrf/mvdma/src/main.c b/tests/boards/nrf/mvdma/src/main.c new file mode 100644 index 0000000000000..992e3aca41c5c --- /dev/null +++ b/tests/boards/nrf/mvdma/src/main.c @@ -0,0 +1,818 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct k_sem done; +static struct k_sem done2; +static void *exp_user_data; +uint32_t t_delta; + +#define SLOW_PERIPH_NODE DT_CHOSEN(zephyr_console) +#define SLOW_PERIPH_MEMORY_SECTION() \ + COND_CODE_1(DT_NODE_HAS_PROP(SLOW_PERIPH_NODE, memory_regions), \ + (__attribute__((__section__(LINKER_DT_NODE_REGION_NAME( \ + DT_PHANDLE(SLOW_PERIPH_NODE, memory_regions)))))), \ + ()) + +#define BUF_LEN 128 +#define REAL_BUF_LEN ROUND_UP(BUF_LEN, CONFIG_DCACHE_LINE_SIZE) + +static uint8_t ram3_buffer1[REAL_BUF_LEN] SLOW_PERIPH_MEMORY_SECTION(); +static uint8_t ram3_buffer2[REAL_BUF_LEN] SLOW_PERIPH_MEMORY_SECTION(); +static uint8_t ram3_buffer3[REAL_BUF_LEN] SLOW_PERIPH_MEMORY_SECTION(); +static uint8_t ram3_buffer4[REAL_BUF_LEN] SLOW_PERIPH_MEMORY_SECTION(); +static uint8_t buffer1[512] __aligned(CONFIG_DCACHE_LINE_SIZE); +static uint8_t buffer2[512] __aligned(CONFIG_DCACHE_LINE_SIZE); +static uint8_t buffer3[512] __aligned(CONFIG_DCACHE_LINE_SIZE); + +static uint32_t get_ts(void) +{ + nrf_timer_task_trigger(NRF_TIMER120, NRF_TIMER_TASK_CAPTURE0); + return nrf_timer_cc_get(NRF_TIMER120, NRF_TIMER_CC_CHANNEL0); +} + +static void mvdma_handler(void *user_data, int status) +{ + uint32_t *ts = user_data; + + if (ts) { + *ts = get_ts(); + } + zassert_equal(user_data, exp_user_data); + k_sem_give(&done); +} + +static void mvdma_handler2(void *user_data, int status) +{ + struct k_sem *sem = user_data; + + k_sem_give(sem); +} + +#define IS_ALIGNED32(x) IS_ALIGNED(x, sizeof(uint32_t)) +static void opt_memcpy(void *dst, void *src, size_t len) +{ + /* If length and addresses are word aligned do the optimized copying. */ + if (IS_ALIGNED32(len) && IS_ALIGNED32(dst) && IS_ALIGNED32(src)) { + for (uint32_t i = 0; i < len / sizeof(uint32_t); i++) { + ((uint32_t *)dst)[i] = ((uint32_t *)src)[i]; + } + return; + } + + memcpy(dst, src, len); +} + +static void dma_run(uint32_t *src_desc, size_t src_len, + uint32_t *sink_desc, size_t sink_len, bool blocking) +{ + int rv; + uint32_t t_start, t, t_int, t_dma_setup, t_end; + struct mvdma_jobs_desc job = { + .source = src_desc, + .source_desc_size = src_len, + .sink = sink_desc, + .sink_desc_size = sink_len, + }; + struct mvdma_ctrl ctrl = NRF_MVDMA_CTRL_INIT(blocking ? NULL : mvdma_handler, &t_int); + + exp_user_data = &t_int; + + t_start = get_ts(); + rv = mvdma_xfer(&ctrl, &job, true); + t_dma_setup = get_ts() - t_start - t_delta; + zassert_equal(rv, 0, "Unexpected rv:%d", rv); + + if (blocking) { + while (mvdma_xfer_check(&ctrl) == -EBUSY) { + } + } else { + rv = k_sem_take(&done, K_MSEC(100)); + } + t_end = get_ts(); + t = t_end - t_start - t_delta; + zassert_equal(rv, 0); + TC_PRINT("DMA setup took %3.2fus\n", (double)t_dma_setup / 320); + if (blocking) { + TC_PRINT("DMA transfer (blocking) %3.2fus\n", (double)t / 320); + } else { + t_int = t_int - t_start - t_delta; + TC_PRINT("DMA transfer (non-blocking) to IRQ:%3.2fus, to thread:%3.2f\n", + (double)t_int / 320, (double)t_int / 320); + } +} + +static void test_memcpy(void *dst, void *src, size_t len, bool frag_dst, bool frag_src, bool blk) +{ + int rv; + int cache_err = IS_ENABLED(CONFIG_DCACHE) ? 0 : -ENOTSUP; + uint32_t t; + + t = get_ts(); + opt_memcpy(dst, src, len); + t = get_ts() - t - t_delta; + TC_PRINT("\nDMA transfer for dst:%p%s src:%p%s length:%d\n", + dst, frag_dst ? "(fragmented)" : "", src, frag_src ? "(fragmented)" : "", + len); + TC_PRINT("CPU copy took %3.2fus\n", (double)t / 320); + + memset(dst, 0, len); + for (size_t i = 0; i < len; i++) { + ((uint8_t *)src)[i] = (uint8_t)i; + } + + uint32_t source_job[] = { + NRF_MVDMA_JOB_DESC(src, len, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_TERMINATE + }; + uint32_t source_job_frag[] = { + NRF_MVDMA_JOB_DESC(src, len / 2, NRF_MVDMA_ATTR_DEFAULT, 0), + /* empty transfer in the middle. */ + NRF_MVDMA_JOB_DESC(1/*dummy addr*/, 0, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_DESC(&((uint8_t *)src)[len / 2], len / 2, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_TERMINATE + }; + uint32_t sink_job[] = { + NRF_MVDMA_JOB_DESC(dst, len, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_TERMINATE + }; + uint32_t sink_job_frag[] = { + NRF_MVDMA_JOB_DESC(dst, len / 2, NRF_MVDMA_ATTR_DEFAULT, 0), + /* empty tranwfer in the middle. */ + NRF_MVDMA_JOB_DESC(1/*dummy addr*/, 0, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_DESC(&((uint8_t *)dst)[len / 2], len / 2, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_TERMINATE + }; + rv = sys_cache_data_flush_range(src, len); + zassert_equal(rv, cache_err); + + dma_run(frag_src ? source_job_frag : source_job, + frag_src ? sizeof(source_job_frag) : sizeof(source_job), + frag_dst ? sink_job_frag : sink_job, + frag_dst ? sizeof(sink_job_frag) : sizeof(sink_job), blk); + + rv = sys_cache_data_invd_range(dst, len); + zassert_equal(rv, cache_err); + + zassert_equal(memcmp(src, dst, len), 0); +} + +static void test_unaligned(uint8_t *dst, uint8_t *src, size_t len, + size_t total_dst, size_t offset_dst) +{ + int rv; + int cache_err = IS_ENABLED(CONFIG_DCACHE) ? 0 : -ENOTSUP; + + memset(dst, 0, total_dst); + for (size_t i = 0; i < len; i++) { + ((uint8_t *)src)[i] = (uint8_t)i; + } + + uint32_t source_job[] = { + NRF_MVDMA_JOB_DESC(src, len, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_TERMINATE + }; + uint32_t sink_job[] = { + NRF_MVDMA_JOB_DESC(&dst[offset_dst], len, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_TERMINATE + }; + struct mvdma_jobs_desc job = { + .source = source_job, + .source_desc_size = sizeof(source_job), + .sink = sink_job, + .sink_desc_size = sizeof(sink_job), + }; + struct mvdma_ctrl ctrl = NRF_MVDMA_CTRL_INIT(mvdma_handler, NULL); + + exp_user_data = NULL; + + rv = sys_cache_data_flush_range(src, len); + zassert_equal(rv, cache_err); + rv = sys_cache_data_flush_range(dst, total_dst); + zassert_equal(rv, cache_err); + + rv = mvdma_xfer(&ctrl, &job, true); + zassert_equal(rv, 0); + + rv = k_sem_take(&done, K_MSEC(100)); + zassert_equal(rv, 0); + + rv = sys_cache_data_invd_range(dst, total_dst); + zassert_equal(rv, cache_err); + + zassert_equal(memcmp(src, &dst[offset_dst], len), 0); + for (size_t i = 0; i < offset_dst; i++) { + zassert_equal(dst[i], 0); + } + for (size_t i = offset_dst + len; i < total_dst; i++) { + zassert_equal(dst[i], 0); + } +} + +ZTEST(mvdma, test_copy_unaligned) +{ + uint8_t src[4] __aligned(CONFIG_DCACHE_LINE_SIZE) = {0xaa, 0xbb, 0xcc, 0xdd}; + uint8_t dst[CONFIG_DCACHE_LINE_SIZE] __aligned(CONFIG_DCACHE_LINE_SIZE); + + for (int i = 1; i < 4; i++) { + for (int j = 1; j < 4; j++) { + test_unaligned(dst, src, i, sizeof(dst), j); + } + } +} + +static void copy_from_slow_periph_mem(bool blocking) +{ + test_memcpy(buffer1, ram3_buffer1, BUF_LEN, false, false, blocking); + test_memcpy(buffer1, ram3_buffer1, BUF_LEN, true, false, blocking); + test_memcpy(buffer1, ram3_buffer1, BUF_LEN, false, true, blocking); + test_memcpy(buffer1, ram3_buffer1, BUF_LEN, true, true, blocking); + test_memcpy(buffer1, ram3_buffer1, 16, false, false, blocking); +} + +ZTEST(mvdma, test_copy_from_slow_periph_mem_blocking) +{ + copy_from_slow_periph_mem(true); +} + +ZTEST(mvdma, test_copy_from_slow_periph_mem_nonblocking) +{ + copy_from_slow_periph_mem(false); +} + +static void copy_to_slow_periph_mem(bool blocking) +{ + test_memcpy(ram3_buffer1, buffer1, BUF_LEN, false, false, blocking); + test_memcpy(ram3_buffer1, buffer1, 16, false, false, blocking); +} + +ZTEST(mvdma, test_copy_to_slow_periph_mem_blocking) +{ + copy_to_slow_periph_mem(true); +} + +ZTEST(mvdma, test_copy_to_slow_periph_mem_nonblocking) +{ + copy_to_slow_periph_mem(false); +} + +ZTEST(mvdma, test_memory_copy) +{ + test_memcpy(buffer1, buffer2, sizeof(buffer1), false, false, true); + test_memcpy(buffer1, buffer2, sizeof(buffer1), false, false, false); +} + +static void test_memcmp(uint8_t *buf1, uint8_t *buf2, size_t len, int line) +{ + if (memcmp(buf1, buf2, len) != 0) { + for (int i = 0; i < len; i++) { + if (buf1[i] != buf2[i]) { + zassert_false(true); + return; + } + } + } +} + +#define APP_RAM0_REGION \ + __attribute__((__section__(LINKER_DT_NODE_REGION_NAME(DT_NODELABEL(app_ram_region))))) \ + __aligned(32) + +static void concurrent_jobs(bool blocking) +{ +#define buf1_src buffer1 +#define buf1_dst ram3_buffer4 +#define buf2_src buffer2 +#define buf2_dst buffer3 + int cache_err = IS_ENABLED(CONFIG_DCACHE) ? 0 : -ENOTSUP; + int rv; + + memset(buf1_dst, 0, BUF_LEN); + memset(buf2_dst, 0, BUF_LEN); + for (size_t i = 0; i < BUF_LEN; i++) { + buf1_src[i] = (uint8_t)i; + buf2_src[i] = (uint8_t)i + 100; + } + bool buf1_src_cached = true; + bool buf1_dst_cached = false; + bool buf2_src_cached = true; + bool buf2_dst_cached = true; + + static uint32_t source_job[] __aligned(CONFIG_DCACHE_LINE_SIZE) = { + NRF_MVDMA_JOB_DESC(buf2_src, BUF_LEN, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_TERMINATE + }; + static uint32_t sink_job[] __aligned(CONFIG_DCACHE_LINE_SIZE) = { + NRF_MVDMA_JOB_DESC(buf2_dst, BUF_LEN, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_TERMINATE + }; + static uint32_t source_job_periph_ram[] __aligned(CONFIG_DCACHE_LINE_SIZE) = { + NRF_MVDMA_JOB_DESC(buf1_src, BUF_LEN, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_TERMINATE + }; + static uint32_t sink_job_periph_ram[] __aligned(CONFIG_DCACHE_LINE_SIZE) = { + NRF_MVDMA_JOB_DESC(buf1_dst, BUF_LEN, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_TERMINATE + }; + struct mvdma_jobs_desc job = { + .source = source_job_periph_ram, + .source_desc_size = sizeof(source_job_periph_ram), + .sink = sink_job_periph_ram, + .sink_desc_size = sizeof(sink_job_periph_ram), + }; + struct mvdma_jobs_desc job2 = { + .source = source_job, + .source_desc_size = sizeof(source_job), + .sink = sink_job, + .sink_desc_size = sizeof(sink_job), + }; + static struct mvdma_ctrl ctrl; + static struct mvdma_ctrl ctrl2; + int rv1 = 0; + int rv2 = 0; + + if (blocking) { + ctrl.handler = NULL; + ctrl2.handler = NULL; + } else { + ctrl.handler = mvdma_handler2; + ctrl.user_data = &done; + ctrl2.handler = mvdma_handler2; + ctrl2.user_data = &done2; + } + + k_sem_init(&done, 0, 1); + k_sem_init(&done2, 0, 1); + + if (buf1_src_cached) { + rv = sys_cache_data_flush_range(buf1_src, BUF_LEN); + zassert_equal(rv, cache_err); + } + + if (buf1_dst_cached) { + rv = sys_cache_data_flush_range(buf1_dst, BUF_LEN); + zassert_equal(rv, cache_err); + } + + if (buf2_src_cached) { + rv = sys_cache_data_flush_range(buf2_src, BUF_LEN); + zassert_equal(rv, cache_err); + } + + if (buf2_dst_cached) { + rv = sys_cache_data_flush_range(buf2_dst, BUF_LEN); + zassert_equal(rv, cache_err); + } + + uint32_t t, t2 = 0, t3 = 0, t4 = 0, t5 = 0; + + t = get_ts(); + rv = mvdma_xfer(&ctrl, &job, true); + zassert_equal(rv, 0, "Unexpected rv:%d", rv); + + t4 = get_ts(); + + rv = mvdma_xfer(&ctrl2, &job2, true); + zassert_true(rv >= 0, "Unexpected rv:%d", rv); + + t5 = get_ts(); + + if (blocking) { + while ((rv1 = mvdma_xfer_check(&ctrl)) == -EBUSY) { + } + t2 = get_ts(); + nrf_mvdma_task_trigger(NRF_MVDMA, NRF_MVDMA_TASK_PAUSE); + while ((rv2 = mvdma_xfer_check(&ctrl2)) == -EBUSY) { + } + t3 = get_ts(); + } else { + rv = k_sem_take(&done, K_MSEC(100)); + zassert_equal(rv, 0); + + rv = k_sem_take(&done2, K_MSEC(100)); + zassert_equal(rv, 0); + } + + if (buf1_dst_cached) { + rv = sys_cache_data_invd_range(buf1_dst, BUF_LEN); + zassert_equal(rv, cache_err); + } + + if (buf2_dst_cached) { + rv = sys_cache_data_invd_range(buf2_dst, BUF_LEN); + zassert_equal(rv, cache_err); + } + + TC_PRINT("%sblocking transfers t1_setup:%d t2_setup:%d t2:%d t3:%d\n", + blocking ? "" : "non", t4 - t, t5 - t, t2 - t, t3 - t); + TC_PRINT("buf1_src:%p buf1_dst:%p buf2_src:%p buf2_dst:%p\n", + buf1_src, buf1_dst, buf2_src, buf2_dst); + TC_PRINT("job1 src:%p sink:%p job2 src:%p sink:%p\n", + source_job_periph_ram, sink_job_periph_ram, source_job, sink_job); + test_memcmp(buf1_src, buf1_dst, BUF_LEN, __LINE__); + test_memcmp(buf2_src, buf2_dst, BUF_LEN, __LINE__); +} + +ZTEST(mvdma, test_concurrent_jobs) +{ + concurrent_jobs(true); + concurrent_jobs(false); +} + +static void concurrent_jobs_check(bool job1_blocking, bool job2_blocking, bool job3_blocking, + bool timing) +{ + int rv; + uint32_t ts1, ts2, ts3, t_memcpy; + + TC_PRINT("mode %s %s\n", !job1_blocking ? "job1 nonblocking" : + !job2_blocking ? "job2 nonblocking" : !job3_blocking ? "job3 nonblocking" : + "all blocking", timing ? "+timing measurement" : ""); + if (timing) { + ts1 = get_ts(); + opt_memcpy(ram3_buffer1, buffer1, BUF_LEN); + opt_memcpy(ram3_buffer2, buffer2, BUF_LEN); + ts2 = get_ts(); + t_memcpy = ts2 - ts1 - t_delta; + TC_PRINT("Memcpy time %d (copying %d to RAM3)\n", t_memcpy, 2 * BUF_LEN); + } + + memset(ram3_buffer1, 0, BUF_LEN); + memset(ram3_buffer2, 0, BUF_LEN); + memset(ram3_buffer3, 0, BUF_LEN); + for (size_t i = 0; i < BUF_LEN; i++) { + buffer1[i] = (uint8_t)i; + buffer2[i] = (uint8_t)i + 100; + buffer3[i] = (uint8_t)i + 200; + } + rv = sys_cache_data_flush_range(buffer1, BUF_LEN); + zassert_equal(rv, 0); + rv = sys_cache_data_flush_range(buffer2, BUF_LEN); + zassert_equal(rv, 0); + rv = sys_cache_data_flush_range(buffer3, BUF_LEN); + zassert_equal(rv, 0); + + uint32_t src_job1[] = { + NRF_MVDMA_JOB_DESC(buffer1, BUF_LEN, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_TERMINATE + }; + uint32_t sink_job1[] = { + NRF_MVDMA_JOB_DESC(ram3_buffer1, BUF_LEN, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_TERMINATE + }; + + uint32_t src_job2[] = { + NRF_MVDMA_JOB_DESC(buffer2, BUF_LEN, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_TERMINATE + }; + uint32_t sink_job2[] = { + NRF_MVDMA_JOB_DESC(ram3_buffer2, BUF_LEN, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_TERMINATE + }; + + uint32_t src_job3[] = { + NRF_MVDMA_JOB_DESC(buffer3, BUF_LEN, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_TERMINATE + }; + uint32_t sink_job3[] = { + NRF_MVDMA_JOB_DESC(ram3_buffer3, BUF_LEN, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_TERMINATE + }; + struct k_sem sem; + struct mvdma_jobs_desc job1 = { + .source = src_job1, + .source_desc_size = sizeof(src_job1), + .sink = sink_job1, + .sink_desc_size = sizeof(sink_job1), + }; + struct mvdma_jobs_desc job2 = { + .source = src_job2, + .source_desc_size = sizeof(src_job2), + .sink = sink_job2, + .sink_desc_size = sizeof(sink_job2), + }; + struct mvdma_jobs_desc job3 = { + .source = src_job3, + .source_desc_size = sizeof(src_job3), + .sink = sink_job3, + .sink_desc_size = sizeof(sink_job3), + }; + struct mvdma_ctrl ctrl1; + struct mvdma_ctrl ctrl2; + struct mvdma_ctrl ctrl3; + + ctrl1.handler = job1_blocking ? NULL : mvdma_handler2; + ctrl2.handler = job2_blocking ? NULL : mvdma_handler2; + ctrl3.handler = job3_blocking ? NULL : mvdma_handler2; + ctrl1.user_data = &sem; + ctrl2.user_data = &sem; + ctrl3.user_data = &sem; + + k_sem_init(&sem, 0, 1); + + ts1 = get_ts(); + + rv = mvdma_xfer(&ctrl1, &job1, true); + zassert_equal(rv, 0, "Unexpected rv:%d", rv); + + rv = mvdma_xfer(&ctrl2, &job2, true); + zassert_true(rv >= 0, "Unexpected rv:%d", rv); + + ts2 = get_ts(); + + if (timing) { + bool eq; + + if (job2_blocking) { + while ((rv = mvdma_xfer_check(&ctrl2)) == -EBUSY) { + } + eq = buffer2[BUF_LEN - 1] == ram3_buffer2[BUF_LEN - 1]; + ts3 = get_ts(); + } else { + rv = k_sem_take(&sem, K_MSEC(100)); + eq = buffer2[BUF_LEN - 1] == ram3_buffer2[BUF_LEN - 1]; + ts3 = get_ts(); + zassert_ok(rv); + } + zassert_true(eq, + "If copying finished (%d), last byte should be there. %02x (exp:%02x)", + rv, ram3_buffer2[BUF_LEN - 1], buffer2[BUF_LEN - 1]); + zassert_true(job1_blocking); + zassert_true(mvdma_xfer_check(&ctrl1) >= 0); + TC_PRINT("Two jobs setup time: %d, from start to finish:%d (%sblocking)\n", + ts2 - ts1 - t_delta, ts3 - ts1 - 2 * t_delta, + job2_blocking ? "" : "non-"); + } else { + rv = job1_blocking ? mvdma_xfer_check(&ctrl1) : k_sem_take(&sem, K_NO_WAIT); + if (rv != -EBUSY) { + + TC_PRINT("t:%d ctrl1:%p ctrl2:%p", ts2 - ts1, ctrl1.handler, ctrl2.handler); + } + zassert_equal(rv, -EBUSY, "Unexpected err:%d", rv); + rv = job2_blocking ? mvdma_xfer_check(&ctrl2) : k_sem_take(&sem, K_NO_WAIT); + zassert_equal(rv, -EBUSY, "Unexpected err:%d", rv); + + k_busy_wait(10000); + } + + test_memcmp(ram3_buffer1, buffer1, BUF_LEN, __LINE__); + test_memcmp(ram3_buffer2, buffer2, BUF_LEN, __LINE__); + + rv = mvdma_xfer(&ctrl3, &job3, true); + zassert_equal(rv, 0, "Unexpected rv:%d", rv); + + if (!timing) { + rv = job1_blocking ? mvdma_xfer_check(&ctrl1) : k_sem_take(&sem, K_NO_WAIT); + zassert_true(rv >= 0, "Unexpected rv:%d", rv); + rv = job2_blocking ? mvdma_xfer_check(&ctrl2) : k_sem_take(&sem, K_NO_WAIT); + zassert_true(rv >= 0, "Unexpected rv:%d", rv); + } + + rv = job3_blocking ? mvdma_xfer_check(&ctrl3) : k_sem_take(&sem, K_NO_WAIT); + zassert_equal(rv, -EBUSY); + + k_busy_wait(10000); + rv = job3_blocking ? mvdma_xfer_check(&ctrl3) : k_sem_take(&sem, K_NO_WAIT); + zassert_true(rv >= 0); + + test_memcmp(ram3_buffer3, buffer3, BUF_LEN, __LINE__); +} + +ZTEST(mvdma, test_concurrent_jobs_check) +{ + concurrent_jobs_check(true, true, true, false); + concurrent_jobs_check(false, true, true, false); + concurrent_jobs_check(true, false, true, false); + concurrent_jobs_check(true, true, false, false); + + concurrent_jobs_check(true, true, true, true); + concurrent_jobs_check(true, false, true, true); +} + +#if DT_SAME_NODE(DT_CHOSEN(zephyr_console), DT_NODELABEL(uart135)) +#define p_reg NRF_UARTE135 +#elif DT_SAME_NODE(DT_CHOSEN(zephyr_console), DT_NODELABEL(uart136)) +#define p_reg NRF_UARTE136 +#else +#error "Not supported" +#endif + +static void peripheral_operation(bool blocking) +{ + static const uint8_t zero; + static const uint32_t evt_err = (uint32_t)&p_reg->EVENTS_ERROR; + static const uint32_t evt_rxto = (uint32_t)&p_reg->EVENTS_RXTO; + static const uint32_t evt_endrx = (uint32_t)&p_reg->EVENTS_DMA.RX.END; + static const uint32_t evt_rxstarted = (uint32_t)&p_reg->EVENTS_DMA.RX.READY; + static const uint32_t evt_txstopped = (uint32_t)&p_reg->EVENTS_TXSTOPPED; + static uint32_t evts[8] __aligned(CONFIG_DCACHE_LINE_SIZE); + + static const int len = 4; + uint32_t source_job_periph_ram[] __aligned(CONFIG_DCACHE_LINE_SIZE) = { + NRF_MVDMA_JOB_DESC(evt_err, len, + NRF_MVDMA_ATTR_DEFAULT, NRF_MVDMA_EXT_ATTR_PERIPH), + NRF_MVDMA_JOB_DESC(&zero, len, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_DESC(evt_endrx, len, + NRF_MVDMA_ATTR_DEFAULT, NRF_MVDMA_EXT_ATTR_PERIPH), + NRF_MVDMA_JOB_DESC(&zero, len, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_DESC(evt_rxto, len, + NRF_MVDMA_ATTR_DEFAULT, NRF_MVDMA_EXT_ATTR_PERIPH), + NRF_MVDMA_JOB_DESC(&zero, len, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_DESC(evt_rxstarted, len, + NRF_MVDMA_ATTR_DEFAULT, NRF_MVDMA_EXT_ATTR_PERIPH), + NRF_MVDMA_JOB_DESC(&zero, len, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_DESC(evt_txstopped, len, + NRF_MVDMA_ATTR_DEFAULT, NRF_MVDMA_EXT_ATTR_PERIPH), + NRF_MVDMA_JOB_TERMINATE + }; + uint32_t sink_job_periph_ram[] __aligned(CONFIG_DCACHE_LINE_SIZE) = { + NRF_MVDMA_JOB_DESC(&evts[0], len, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_DESC(evt_err, len, + NRF_MVDMA_ATTR_DEFAULT, NRF_MVDMA_EXT_ATTR_PERIPH), + NRF_MVDMA_JOB_DESC(&evts[1], len, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_DESC(evt_endrx, len, + NRF_MVDMA_ATTR_DEFAULT, NRF_MVDMA_EXT_ATTR_PERIPH), + NRF_MVDMA_JOB_DESC(&evts[2], len, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_DESC(evt_rxto, len, + NRF_MVDMA_ATTR_DEFAULT, NRF_MVDMA_EXT_ATTR_PERIPH), + NRF_MVDMA_JOB_DESC(&evts[3], len, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_DESC(evt_rxstarted, len, + NRF_MVDMA_ATTR_DEFAULT, NRF_MVDMA_EXT_ATTR_PERIPH), + NRF_MVDMA_JOB_DESC(&evts[4], len, NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_TERMINATE + }; + + TC_PRINT("Reading and clearing UARTE events (9 peripheral op)."); + dma_run(source_job_periph_ram, sizeof(source_job_periph_ram), + sink_job_periph_ram, sizeof(sink_job_periph_ram), blocking); + for (int i = 0; i < 8; i++) { + TC_PRINT("evt%d:%d ", i, evts[i]); + } + TC_PRINT("\n"); +} + +ZTEST(mvdma, test_peripheral_operation_blocking) +{ + peripheral_operation(true); +} + +ZTEST(mvdma, test_peripheral_operation_nonblocking) +{ + peripheral_operation(false); +} + +static void mix_periph_slow_ram(bool blocking) +{ + int rv; + uint32_t t1; + static uint8_t tx_buffer[] __aligned(4) = "tst buf which contain 32bytes\r\n"; + static uint8_t tx_buffer_ram3[40] SLOW_PERIPH_MEMORY_SECTION(); + static uint32_t xfer_data[] __aligned(CONFIG_DCACHE_LINE_SIZE) = { + (uint32_t)tx_buffer_ram3, + sizeof(tx_buffer), + 1 + }; + uint32_t source_job[] __aligned(CONFIG_DCACHE_LINE_SIZE) = { + NRF_MVDMA_JOB_DESC(tx_buffer, sizeof(tx_buffer), NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_DESC(xfer_data, sizeof(xfer_data), NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_TERMINATE + }; + uint32_t sink_job[] __aligned(CONFIG_DCACHE_LINE_SIZE) = { + NRF_MVDMA_JOB_DESC(tx_buffer_ram3, sizeof(tx_buffer), NRF_MVDMA_ATTR_DEFAULT, 0), + NRF_MVDMA_JOB_DESC((uint32_t)&p_reg->DMA.TX.PTR, 2 * sizeof(uint32_t), + NRF_MVDMA_ATTR_DEFAULT, NRF_MVDMA_EXT_ATTR_PERIPH), + NRF_MVDMA_JOB_DESC((uint32_t)&p_reg->TASKS_DMA.TX.START, sizeof(uint32_t), + NRF_MVDMA_ATTR_DEFAULT, NRF_MVDMA_EXT_ATTR_PERIPH), + NRF_MVDMA_JOB_TERMINATE + }; + + memset(tx_buffer_ram3, 'a', sizeof(tx_buffer_ram3)); + + TC_PRINT("MVDMA buffer copy and transfer trigger. RAM3 buffer:%p RAM0 buffer:%p\n", + tx_buffer_ram3, tx_buffer); + rv = sys_cache_data_flush_range(tx_buffer, sizeof(tx_buffer)); + zassert_equal(rv, 0); + dma_run(source_job, sizeof(source_job), sink_job, sizeof(sink_job), blocking); + + k_msleep(10); + TC_PRINT("Manual operation test\n"); + memset(tx_buffer_ram3, 'a', sizeof(tx_buffer_ram3)); + k_msleep(10); + + t1 = get_ts(); + opt_memcpy(tx_buffer_ram3, tx_buffer, sizeof(tx_buffer)); + p_reg->DMA.TX.PTR = (uint32_t)tx_buffer_ram3; + p_reg->DMA.TX.MAXCNT = sizeof(tx_buffer); + p_reg->TASKS_DMA.TX.START = 1; + t1 = get_ts() - t1 - t_delta; + k_msleep(10); + TC_PRINT("Manual operation took:%3.2f us\n", (double)t1 / 320); +} + +ZTEST(mvdma, test_mix_periph_slow_ram_blocking) +{ + mix_periph_slow_ram(true); +} + +ZTEST(mvdma, test_mix_periph_slow_ram_nonblocking) +{ + mix_periph_slow_ram(false); +} + +ZTEST(mvdma, test_simple_xfer) +{ + struct mvdma_basic_desc desc __aligned(CONFIG_DCACHE_LINE_SIZE) = + NRF_MVDMA_BASIC_MEMCPY_INIT(buffer2, buffer1, BUF_LEN); + int rv; + uint32_t t; + + /* Run twice to get the timing result when code is cached. Timing of the first run + * depends on previous test cases. + */ + for (int i = 0; i < 2; i++) { + struct mvdma_ctrl ctrl = NRF_MVDMA_CTRL_INIT(NULL, NULL); + + zassert_true(IS_ALIGNED(&desc, CONFIG_DCACHE_LINE_SIZE)); + memset(buffer1, 0xaa, BUF_LEN); + memset(buffer2, 0xbb, BUF_LEN); + sys_cache_data_flush_range(buffer1, BUF_LEN); + sys_cache_data_flush_range(buffer2, BUF_LEN); + + t = get_ts(); + rv = mvdma_basic_xfer(&ctrl, &desc, false); + t = get_ts() - t; + zassert_ok(rv); + + k_busy_wait(1000); + rv = mvdma_xfer_check(&ctrl); + zassert_true(rv >= 0); + + sys_cache_data_invd_range(buffer2, BUF_LEN); + test_memcmp(buffer1, buffer2, BUF_LEN, __LINE__); + } + + TC_PRINT("MVDMA memcpy setup (code cached) took:%d (%3.2fus)\n", t, (double)t / 320); +} + +ZTEST(mvdma, test_simple_zero_fill) +{ + struct mvdma_basic_desc desc __aligned(CONFIG_DCACHE_LINE_SIZE) = + NRF_MVDMA_BASIC_ZERO_INIT(buffer1, BUF_LEN); + struct mvdma_ctrl ctrl = NRF_MVDMA_CTRL_INIT(NULL, NULL); + int rv; + + memset(buffer1, 0xaa, BUF_LEN); + sys_cache_data_flush_range(buffer1, BUF_LEN); + rv = mvdma_basic_xfer(&ctrl, &desc, false); + zassert_ok(rv); + + k_busy_wait(1000); + rv = mvdma_xfer_check(&ctrl); + zassert_true(rv >= 0); + + /* DMA shall fill the buffer with 0's. */ + sys_cache_data_invd_range(buffer1, BUF_LEN); + for (int i = 0; i < BUF_LEN; i++) { + zassert_equal(buffer1[i], 0); + } +} + +static void before(void *unused) +{ + uint32_t t_delta2; + + nrf_timer_bit_width_set(NRF_TIMER120, NRF_TIMER_BIT_WIDTH_32); + nrf_timer_prescaler_set(NRF_TIMER120, 0); + nrf_timer_task_trigger(NRF_TIMER120, NRF_TIMER_TASK_START); + + t_delta = get_ts(); + t_delta = get_ts() - t_delta; + + t_delta2 = get_ts(); + t_delta2 = get_ts() - t_delta2; + + t_delta = MIN(t_delta2, t_delta); + + nrf_gpio_cfg_output(9*32); + nrf_gpio_cfg_output(9*32+1); + nrf_gpio_cfg_output(9*32+2); + nrf_gpio_cfg_output(9*32+3); + k_sem_init(&done, 0, 1); + k_sem_init(&done2, 0, 1); +} + +ZTEST_SUITE(mvdma, NULL, NULL, before, NULL, NULL); diff --git a/tests/boards/nrf/mvdma/testcase.yaml b/tests/boards/nrf/mvdma/testcase.yaml new file mode 100644 index 0000000000000..ae9110b8577c2 --- /dev/null +++ b/tests/boards/nrf/mvdma/testcase.yaml @@ -0,0 +1,11 @@ +tests: + boards.nrf.mvdma: + tags: + - drivers + - dma + harness: ztest + platform_allow: + - nrf54h20dk/nrf54h20/cpuapp + - nrf54h20dk/nrf54h20/cpurad + integration_platforms: + - nrf54h20dk/nrf54h20/cpuapp