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:
parent
3ebae000e5
commit
a0777734de
5 changed files with 296 additions and 2 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
184
drivers/firmware/nrf_ironside/dvfs.c
Normal file
184
drivers/firmware/nrf_ironside/dvfs.c
Normal 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;
|
||||
}
|
92
include/zephyr/drivers/firmware/nrf_ironside/dvfs.h
Normal file
92
include/zephyr/drivers/firmware/nrf_ironside/dvfs.h
Normal 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_ */
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue