drivers: nrf_ironside dvfs service

Added handling of new IRONside DVFS service.
NRFS DVFS is now not enabled by default.

Signed-off-by: Łukasz Stępnicki <lukasz.stepnicki@nordicsemi.no>
This commit is contained in:
Łukasz Stępnicki 2025-05-22 13:36:31 +02:00 committed by Benjamin Cabé
commit a0777734de
5 changed files with 296 additions and 2 deletions

View file

@ -8,3 +8,4 @@ zephyr_library_sources_ifdef(CONFIG_NRF_IRONSIDE_CALL call.c)
zephyr_library_sources_ifdef(CONFIG_NRF_IRONSIDE_BOOT_REPORT boot_report.c)
zephyr_library_sources_ifdef(CONFIG_NRF_IRONSIDE_CPUCONF_SERVICE cpuconf.c)
zephyr_library_sources_ifdef(CONFIG_NRF_IRONSIDE_UPDATE_SERVICE update.c)
zephyr_library_sources_ifdef(CONFIG_NRF_IRONSIDE_DVFS_SERVICE dvfs.c)

View file

@ -50,4 +50,21 @@ config NRF_IRONSIDE_BOOT_REPORT
help
Support for parsing the Boot Report populated by Nordic IRONside firmware.
config NRF_IRONSIDE_DVFS_SERVICE
bool "IRONside DVFS service"
depends on SOC_NRF54H20_CPUAPP
select NRF_IRONSIDE_CALL
help
Service used to handle DVFS operating point requests.
if NRF_IRONSIDE_DVFS_SERVICE
config NRF_IRONSIDE_DVFS_OPPOINT_CHANGE_MUTEX_TIMEOUT_MS
int "IRONSside DVFS change oppoint mutex timeout"
default 100
help
Maximum tiemout when waiting for DVFS oppoint change mutex lock.
endif # NRF_IRONSIDE_DVFS_SERVICE
endmenu

View file

@ -0,0 +1,184 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
* SPDX-License-Identifier: Apache-2.0
*/
#include <hal/nrf_hsfll.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/firmware/nrf_ironside/dvfs.h>
#include <zephyr/drivers/firmware/nrf_ironside/call.h>
static enum ironside_dvfs_oppoint current_dvfs_oppoint = IRONSIDE_DVFS_OPP_HIGH;
struct dvfs_hsfll_data_t {
uint32_t new_f_mult;
uint32_t new_f_trim_entry;
uint32_t max_hsfll_freq;
};
static const struct dvfs_hsfll_data_t dvfs_hsfll_data[] = {
/* ABB oppoint 0.8V */
{
.new_f_mult = 20,
.new_f_trim_entry = 0,
.max_hsfll_freq = 320000000,
},
/* ABB oppoint 0.6V */
{
.new_f_mult = 8,
.new_f_trim_entry = 2,
.max_hsfll_freq = 128000000,
},
/* ABB oppoint 0.5V */
{
.new_f_mult = 4,
.new_f_trim_entry = 3,
.max_hsfll_freq = 64000000,
},
};
BUILD_ASSERT(ARRAY_SIZE(dvfs_hsfll_data) == (IRONSIDE_DVFS_OPPOINT_COUNT),
"dvfs_hsfll_data size must match number of DVFS oppoints");
/**
* @brief Check if the requested oppoint change operation is downscaling.
*
* @param target_freq_setting The target oppoint to check.
* @return true if the current oppoint is higher than the target, false otherwise.
*/
static bool ironside_dvfs_is_downscaling(enum ironside_dvfs_oppoint target_freq_setting)
{
return current_dvfs_oppoint < target_freq_setting;
}
/**
* @brief Configure hsfll depending on selected oppoint
*
* @param enum oppoint target operation point
*/
static void ironside_dvfs_configure_hsfll(enum ironside_dvfs_oppoint oppoint)
{
nrf_hsfll_trim_t hsfll_trim = {};
uint8_t freq_trim_idx = dvfs_hsfll_data[oppoint].new_f_trim_entry;
#if defined(NRF_APPLICATION)
hsfll_trim.vsup = NRF_FICR->TRIM.APPLICATION.HSFLL.TRIM.VSUP;
hsfll_trim.coarse = NRF_FICR->TRIM.APPLICATION.HSFLL.TRIM.COARSE[freq_trim_idx];
hsfll_trim.fine = NRF_FICR->TRIM.APPLICATION.HSFLL.TRIM.FINE[freq_trim_idx];
#if NRF_HSFLL_HAS_TCOEF_TRIM
hsfll_trim.tcoef = NRF_FICR->TRIM.APPLICATION.HSFLL.TRIM.TCOEF;
#endif
#else
#error "Only application core is supported for DVFS"
#endif
nrf_hsfll_clkctrl_mult_set(NRF_HSFLL, dvfs_hsfll_data[oppoint].new_f_mult);
nrf_hsfll_trim_set(NRF_HSFLL, &hsfll_trim);
nrf_barrier_w();
nrf_hsfll_task_trigger(NRF_HSFLL, NRF_HSFLL_TASK_FREQ_CHANGE);
/* Trigger hsfll task one more time, SEE PAC-4078 */
nrf_hsfll_task_trigger(NRF_HSFLL, NRF_HSFLL_TASK_FREQ_CHANGE);
}
/* Function handling steps for DVFS oppoint change. */
static void ironside_dvfs_prepare_to_scale(enum ironside_dvfs_oppoint dvfs_oppoint)
{
if (ironside_dvfs_is_downscaling(dvfs_oppoint)) {
ironside_dvfs_configure_hsfll(dvfs_oppoint);
}
}
/* Update MDK variable which is used by nrfx_coredep_delay_us (k_busy_wait). */
static void ironside_dvfs_update_core_clock(enum ironside_dvfs_oppoint dvfs_oppoint)
{
extern uint32_t SystemCoreClock;
SystemCoreClock = dvfs_hsfll_data[dvfs_oppoint].max_hsfll_freq;
}
/* Perform scaling finnish procedure. */
static void ironside_dvfs_change_oppoint_complete(enum ironside_dvfs_oppoint dvfs_oppoint)
{
if (!ironside_dvfs_is_downscaling(dvfs_oppoint)) {
ironside_dvfs_configure_hsfll(dvfs_oppoint);
}
current_dvfs_oppoint = dvfs_oppoint;
ironside_dvfs_update_core_clock(dvfs_oppoint);
}
/**
* @brief Check if ABB analog part is locked.
*
* @param abb Pointer to ABB peripheral.
*
* @return true if ABB is locked, false otherwise.
*/
static inline bool ironside_dvfs_is_abb_locked(NRF_ABB_Type *abb)
{
/* Check if ABB analog part is locked. */
return ((abb->STATUSANA & ABB_STATUSANA_LOCKED_Msk) != 0);
}
/**
* @brief Request DVFS oppoint change from IRONside secure domain.
* This function will send a request over IPC to the IRONside secure domain
* This function is synchronous and will return when the request is completed.
*
* @param oppoint @ref enum ironside_dvfs_oppoint
* @return int
*/
static int ironside_dvfs_req_oppoint(enum ironside_dvfs_oppoint oppoint)
{
int err;
struct ironside_call_buf *const buf = ironside_call_alloc();
buf->id = IRONSIDE_CALL_ID_DVFS_SERVICE_V0;
buf->args[IRONSIDE_DVFS_SERVICE_OPPOINT_IDX] = oppoint;
ironside_call_dispatch(buf);
if (buf->status == IRONSIDE_CALL_STATUS_RSP_SUCCESS) {
err = buf->args[IRONSIDE_DVFS_SERVICE_RETCODE_IDX];
} else {
err = buf->status;
}
ironside_call_release(buf);
return err;
}
int ironside_dvfs_change_oppoint(enum ironside_dvfs_oppoint dvfs_oppoint)
{
int status = 0;
if (!ironside_dvfs_is_oppoint_valid(dvfs_oppoint)) {
return -IRONSIDE_DVFS_ERROR_WRONG_OPPOINT;
}
if (!ironside_dvfs_is_abb_locked(NRF_ABB)) {
return -IRONSIDE_DVFS_ERROR_BUSY;
}
if (dvfs_oppoint == current_dvfs_oppoint) {
return status;
}
if (k_is_in_isr()) {
return -IRONSIDE_DVFS_ERROR_ISR_NOT_ALLOWED;
}
ironside_dvfs_prepare_to_scale(dvfs_oppoint);
status = ironside_dvfs_req_oppoint(dvfs_oppoint);
if (status != 0) {
return status;
}
ironside_dvfs_change_oppoint_complete(dvfs_oppoint);
return status;
}

View file

@ -0,0 +1,92 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_NRF_IRONSIDE_DVFS_H_
#define ZEPHYR_INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_NRF_IRONSIDE_DVFS_H_
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <errno.h>
enum ironside_dvfs_oppoint {
IRONSIDE_DVFS_OPP_HIGH = 0,
IRONSIDE_DVFS_OPP_MEDLOW = 1,
IRONSIDE_DVFS_OPP_LOW = 2
};
/**
* @brief Number of DVFS oppoints supported by IRONside.
*
* This is the number of different DVFS oppoints that can be set on IRONside.
* The oppoints are defined in the `ironside_dvfs_oppoint` enum.
*/
#define IRONSIDE_DVFS_OPPOINT_COUNT (3)
/**
* @name IRONside DVFS service error codes.
* @{
*/
/** The requested DVFS oppoint is not allowed. */
#define IRONSIDE_DVFS_ERROR_WRONG_OPPOINT (1)
/** Waiting for mutex lock timed out, or hardware is busy. */
#define IRONSIDE_DVFS_ERROR_BUSY (2)
/** There is configuration error in the DVFS service. */
#define IRONSIDE_DVFS_ERROR_OPPOINT_DATA (3)
/** The caller does not have permission to change the DVFS oppoint. */
#define IRONSIDE_DVFS_ERROR_PERMISSION (4)
/** The requested DVFS oppoint is already set, no change needed. */
#define IRONSIDE_DVFS_ERROR_NO_CHANGE_NEEDED (5)
/** The operation timed out, possibly due to a hardware issue. */
#define IRONSIDE_DVFS_ERROR_TIMEOUT (6)
/** The DVFS oppoint change operation is not allowed in the ISR context. */
#define IRONSIDE_DVFS_ERROR_ISR_NOT_ALLOWED (7)
/**
* @}
*/
/* IRONside call identifiers with implicit versions.
*
* With the initial "version 0", the service ABI is allowed to break until the
* first production release of IRONside SE.
*/
#define IRONSIDE_CALL_ID_DVFS_SERVICE_V0 3
/* Index of the DVFS oppoint within the service buffer. */
#define IRONSIDE_DVFS_SERVICE_OPPOINT_IDX (0)
/* Index of the return code within the service buffer. */
#define IRONSIDE_DVFS_SERVICE_RETCODE_IDX (0)
/**
* @brief Change the current DVFS oppoint.
*
* This function will request a change of the current DVFS oppoint to the
* specified value. It will block until the change is applied.
*
* @param dvfs_oppoint The new DVFS oppoint to set.
* @return int 0 on success, negative error code on failure.
*/
int ironside_dvfs_change_oppoint(enum ironside_dvfs_oppoint dvfs_oppoint);
/**
* @brief Check if the given oppoint is valid.
*
* @param dvfs_oppoint The oppoint to check.
* @return true if the oppoint is valid, false otherwise.
*/
static inline bool ironside_dvfs_is_oppoint_valid(enum ironside_dvfs_oppoint dvfs_oppoint)
{
if (dvfs_oppoint != IRONSIDE_DVFS_OPP_HIGH &&
dvfs_oppoint != IRONSIDE_DVFS_OPP_MEDLOW &&
dvfs_oppoint != IRONSIDE_DVFS_OPP_LOW) {
return false;
}
return true;
}
#endif /* ZEPHYR_INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_NRF_IRONSIDE_DVFS_H_ */

View file

@ -14,12 +14,12 @@ if(CONFIG_NRFS)
zephyr_include_directories(${INC_DIR})
zephyr_include_directories(${INC_DIR}/services)
zephyr_include_directories(${HELPERS_DIR})
zephyr_include_directories_ifdef(CONFIG_NRFS_HAS_DVFS_SERVICE ${HELPERS_DIR})
zephyr_include_directories(.)
zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/backends)
zephyr_include_directories_ifdef(CONFIG_NRFS_DVFS_LOCAL_DOMAIN ${CMAKE_CURRENT_SOURCE_DIR}/dvfs)
zephyr_library_sources(${HELPERS_DIR}/dvfs_oppoint.c)
zephyr_library_sources_ifdef(CONFIG_NRFS_HAS_DVFS_SERVICE ${HELPERS_DIR}/dvfs_oppoint.c)
if(CONFIG_NRFS_LOCAL_DOMAIN)
zephyr_library_sources_ifdef(CONFIG_NRFS_AUDIOPLL_SERVICE_ENABLED ${SRC_DIR}/services/nrfs_audiopll.c)