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>; 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/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/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; } 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. 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; } 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