blob: 20e879c4f737eaaf4aecd484f7827982b32f4255 [file] [log] [blame]
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "ld_dvfs_handler.h"
#include "ld_dvfs.h"
#include <hal/nrf_hsfll.h>
#include <nrfs_dvfs.h>
#include <nrfs_backend_ipc_service.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(LD_DVFS_LIB, CONFIG_LOCAL_DOMAIN_DVFS_LIB_LOG_LEVEL);
static K_SEM_DEFINE(dvfs_service_sync_sem, 0, 1);
static K_SEM_DEFINE(dvfs_service_idle_sem, 0, 1);
#define DVFS_SERV_HDL_INIT_DONE_BIT_POS (0)
#define DVFS_SERV_HDL_FREQ_CHANGE_IN_PROGRESS_BIT_POS (1)
static atomic_t dvfs_service_handler_state_bits;
static enum dvfs_frequency_setting current_freq_setting;
static void dvfs_service_handler_set_state_bit(uint32_t bit_pos)
{
atomic_set_bit(&dvfs_service_handler_state_bits, bit_pos);
}
static void dvfs_service_handler_clear_state_bit(uint32_t bit_pos)
{
atomic_clear_bit(&dvfs_service_handler_state_bits, bit_pos);
}
static bool dvfs_service_handler_get_state_bit(uint32_t bit_pos)
{
return atomic_test_bit(&dvfs_service_handler_state_bits, bit_pos);
}
static bool dvfs_service_handler_init_done(void)
{
return dvfs_service_handler_get_state_bit(DVFS_SERV_HDL_INIT_DONE_BIT_POS);
}
static bool dvfs_service_handler_freq_change_in_progress(void)
{
return dvfs_service_handler_get_state_bit(DVFS_SERV_HDL_FREQ_CHANGE_IN_PROGRESS_BIT_POS);
}
static void dvfs_service_handler_nrfs_error_check(nrfs_err_t err)
{
if (err != NRFS_SUCCESS) {
LOG_ERR("Failed with nrfs error: %d", err);
}
}
static void dvfs_service_handler_error(int err)
{
if (err != 0) {
LOG_ERR("Failed with error: %d", err);
}
}
static uint32_t *get_next_context(void)
{
static uint32_t ctx;
ctx++;
return &ctx;
}
static bool dvfs_service_handler_freq_setting_allowed(enum dvfs_frequency_setting freq_setting)
{
if (freq_setting == DVFS_FREQ_HIGH || freq_setting == DVFS_FREQ_MEDLOW ||
freq_setting == DVFS_FREQ_LOW) {
return true;
}
return false;
}
static enum dvfs_frequency_setting dvfs_service_handler_get_current_oppoint(void)
{
LOG_INF("Current LD freq setting: %d", current_freq_setting);
return current_freq_setting;
}
/* Function to check if current operation is down-scaling */
static bool dvfs_service_handler_is_downscaling(enum dvfs_frequency_setting target_freq_setting)
{
if (dvfs_service_handler_freq_setting_allowed(target_freq_setting)) {
LOG_DBG("Checking if downscaling %s",
(dvfs_service_handler_get_current_oppoint() < target_freq_setting) ? "YES" :
"NO");
return dvfs_service_handler_get_current_oppoint() < target_freq_setting;
}
return false;
}
/* Function handling steps for scaling preparation. */
static void dvfs_service_handler_prepare_to_scale(enum dvfs_frequency_setting oppoint_freq)
{
LOG_INF("Prepare to scale, oppoint freq %d", oppoint_freq);
enum dvfs_frequency_setting new_oppoint = oppoint_freq;
enum dvfs_frequency_setting current_oppoint = dvfs_service_handler_get_current_oppoint();
if (new_oppoint == current_oppoint) {
LOG_INF("New oppoint is same as previous, no change");
} else {
ld_dvfs_configure_abb_for_transition(current_oppoint, new_oppoint);
if (dvfs_service_handler_is_downscaling(new_oppoint)) {
int32_t err = ld_dvfs_configure_hsfll(new_oppoint);
if (err != 0) {
dvfs_service_handler_error(err);
}
}
}
}
/* Do background job during scaling process (e.g. increased power consumption during down-scale). */
static void dvfs_service_handler_scaling_background_job(enum dvfs_frequency_setting oppoint_freq)
{
LOG_INF("Perform scaling background job if needed.");
if (dvfs_service_handler_is_downscaling(oppoint_freq)) {
k_sem_give(&dvfs_service_idle_sem);
}
}
/* Perform scaling finnish procedure. */
static void dvfs_service_handler_scaling_finish(enum dvfs_frequency_setting oppoint_freq)
{
LOG_INF("Scaling finnish oppoint freq %d", oppoint_freq);
ld_dvfs_scaling_finish(dvfs_service_handler_is_downscaling(oppoint_freq));
if (!dvfs_service_handler_is_downscaling(oppoint_freq)) {
int32_t err = ld_dvfs_configure_hsfll(oppoint_freq);
if (err != 0) {
dvfs_service_handler_error(err);
}
}
current_freq_setting = oppoint_freq;
}
/* Function to set hsfll to highest frequency when switched to ABB. */
static void dvfs_service_handler_set_initial_hsfll_config(void)
{
int32_t err = ld_dvfs_configure_hsfll(DVFS_FREQ_HIGH);
current_freq_setting = DVFS_FREQ_HIGH;
if (err != 0) {
dvfs_service_handler_error(err);
}
}
/* DVFS event handler callback function.*/
static void nrfs_dvfs_evt_handler(nrfs_dvfs_evt_t const *p_evt, void *context)
{
LOG_INF("%s", __func__);
switch (p_evt->type) {
case NRFS_DVFS_EVT_INIT_PREPARATION:
LOG_INF("DVFS handler EVT_INIT_PREPARATION");
#if defined(NRF_SECURE)
ld_dvfs_clear_zbb();
dvfs_service_handler_nrfs_error_check(
nrfs_dvfs_init_complete_request(get_next_context()));
LOG_INF("DVFS handler EVT_INIT_PREPARATION handled");
#else
LOG_ERR("DVFS handler - unexpected EVT_INIT_PREPARATION");
#endif
break;
case NRFS_DVFS_EVT_INIT_DONE:
LOG_INF("DVFS handler EVT_INIT_DONE");
dvfs_service_handler_set_initial_hsfll_config();
dvfs_service_handler_set_state_bit(DVFS_SERV_HDL_INIT_DONE_BIT_POS);
k_sem_give(&dvfs_service_sync_sem);
LOG_INF("DVFS handler EVT_INIT_DONE handled");
break;
case NRFS_DVFS_EVT_OPPOINT_REQ_CONFIRMED:
/* Optional confirmation from sysctrl, wait for oppoint.*/
LOG_INF("DVFS handler EVT_OPPOINT_REQ_CONFIRMED");
break;
case NRFS_DVFS_EVT_OPPOINT_SCALING_PREPARE:
/*Target oppoint will be received here.*/
LOG_INF("DVFS handler EVT_OPPOINT_SCALING_PREPARE");
#if !defined(NRF_SECURE)
if (dvfs_service_handler_is_downscaling(p_evt->freq)) {
#endif
dvfs_service_handler_prepare_to_scale(p_evt->freq);
dvfs_service_handler_nrfs_error_check(
nrfs_dvfs_ready_to_scale(get_next_context()));
dvfs_service_handler_scaling_background_job(p_evt->freq);
LOG_INF("DVFS handler EVT_OPPOINT_SCALING_PREPARE handled");
#if !defined(NRF_SECURE)
current_freq_setting = p_evt->freq;
} else {
LOG_ERR("DVFS handler - unexpected EVT_OPPOINT_SCALING_PREPARE");
}
#endif
break;
case NRFS_DVFS_EVT_OPPOINT_SCALING_DONE:
LOG_INF("DVFS handler EVT_OPPOINT_SCALING_DONE");
dvfs_service_handler_clear_state_bit(DVFS_SERV_HDL_FREQ_CHANGE_IN_PROGRESS_BIT_POS);
dvfs_service_handler_scaling_finish(p_evt->freq);
LOG_INF("DVFS handler EVT_OPPOINT_SCALING_DONE handled");
break;
case NRFS_DVFS_EVT_REJECT:
LOG_ERR("DVFS handler - request rejected");
break;
default:
LOG_ERR("DVFS handler - unexpected event: 0x%x", p_evt->type);
break;
}
}
/* Task to handle dvfs init procedure. */
static void dvfs_service_handler_task(void *dummy0, void *dummy1, void *dummy2)
{
ARG_UNUSED(dummy0);
ARG_UNUSED(dummy1);
ARG_UNUSED(dummy2);
LOG_INF("Trim ABB for default voltage.");
ld_dvfs_init();
LOG_INF("Waiting for backend init");
/* Wait for ipc initialization */
nrfs_backend_wait_for_connection(K_FOREVER);
nrfs_err_t status;
LOG_INF("nrfs_dvfs_init");
status = nrfs_dvfs_init(nrfs_dvfs_evt_handler);
dvfs_service_handler_nrfs_error_check(status);
LOG_INF("nrfs_dvfs_init_prepare_request");
status = nrfs_dvfs_init_prepare_request(get_next_context());
dvfs_service_handler_nrfs_error_check(status);
/* Wait for init*/
k_sem_take(&dvfs_service_sync_sem, K_FOREVER);
LOG_INF("DVFS init done.");
#if defined(CONFIG_NRFS_LOCAL_DOMAIN_DVFS_SCALE_DOWN_AFTER_INIT)
LOG_INF("Requesting lowest frequency oppoint.");
dvfs_service_handler_change_freq_setting(DVFS_FREQ_LOW);
#endif
while (1) {
k_sem_take(&dvfs_service_idle_sem, K_FOREVER);
/* perform background processing */
ld_dvfs_scaling_background_process(true);
}
}
K_THREAD_DEFINE(dvfs_service_handler_task_id,
CONFIG_NRFS_LOCAL_DOMAIN_DVFS_HANDLER_TASK_STACK_SIZE,
dvfs_service_handler_task,
NULL,
NULL,
NULL,
CONFIG_NRFS_LOCAL_DOMAIN_DVFS_HANDLER_TASK_PRIORITY,
0,
0);
int32_t dvfs_service_handler_change_freq_setting(enum dvfs_frequency_setting freq_setting)
{
if (!dvfs_service_handler_init_done()) {
LOG_INF("Init not done!");
return -EAGAIN;
}
if (dvfs_service_handler_freq_change_in_progress()) {
LOG_INF("Frequency change in progress.");
return -EBUSY;
}
if (!dvfs_service_handler_freq_setting_allowed(freq_setting)) {
return -ENXIO;
}
nrfs_err_t status = nrfs_dvfs_oppoint_request(freq_setting, get_next_context());
dvfs_service_handler_nrfs_error_check(status);
return status;
}