| 
 | 1 | +/*  | 
 | 2 | + * Copyright (c) 2025 Nordic Semiconductor ASA  | 
 | 3 | + * SPDX-License-Identifier: Apache-2.0  | 
 | 4 | + */  | 
 | 5 | +#include <hal/nrf_hsfll.h>  | 
 | 6 | +#include <zephyr/kernel.h>  | 
 | 7 | + | 
 | 8 | +#include <zephyr/drivers/firmware/nrf_ironside/dvfs.h>  | 
 | 9 | +#include <zephyr/drivers/firmware/nrf_ironside/call.h>  | 
 | 10 | + | 
 | 11 | +static enum ironside_dvfs_oppoint current_dvfs_oppoint = IRONSIDE_DVFS_OPP_HIGH;  | 
 | 12 | + | 
 | 13 | +struct dvfs_hsfll_data_t {  | 
 | 14 | +	uint32_t new_f_mult;  | 
 | 15 | +	uint32_t new_f_trim_entry;  | 
 | 16 | +	uint32_t max_hsfll_freq;  | 
 | 17 | +};  | 
 | 18 | + | 
 | 19 | +static const struct dvfs_hsfll_data_t dvfs_hsfll_data[] = {  | 
 | 20 | +	/* ABB oppoint 0.8V */  | 
 | 21 | +	{  | 
 | 22 | +		.new_f_mult = 20,  | 
 | 23 | +		.new_f_trim_entry = 0,  | 
 | 24 | +		.max_hsfll_freq = 320000000,  | 
 | 25 | +	},  | 
 | 26 | +	/* ABB oppoint 0.6V */  | 
 | 27 | +	{  | 
 | 28 | +		.new_f_mult = 8,  | 
 | 29 | +		.new_f_trim_entry = 2,  | 
 | 30 | +		.max_hsfll_freq = 128000000,  | 
 | 31 | +	},  | 
 | 32 | +	/* ABB oppoint 0.5V */  | 
 | 33 | +	{  | 
 | 34 | +		.new_f_mult = 4,  | 
 | 35 | +		.new_f_trim_entry = 3,  | 
 | 36 | +		.max_hsfll_freq = 64000000,  | 
 | 37 | +	},  | 
 | 38 | +};  | 
 | 39 | + | 
 | 40 | +BUILD_ASSERT(ARRAY_SIZE(dvfs_hsfll_data) == (IRONSIDE_DVFS_OPPOINT_COUNT),  | 
 | 41 | +	     "dvfs_hsfll_data size must match number of DVFS oppoints");  | 
 | 42 | + | 
 | 43 | +/**  | 
 | 44 | + * @brief Check if the requested oppoint change operation is downscaling.  | 
 | 45 | + *  | 
 | 46 | + * @param target_freq_setting The target oppoint to check.  | 
 | 47 | + * @return true if the current oppoint is higher than the target, false otherwise.  | 
 | 48 | + */  | 
 | 49 | +static bool ironside_dvfs_is_downscaling(enum ironside_dvfs_oppoint target_freq_setting)  | 
 | 50 | +{  | 
 | 51 | +	return current_dvfs_oppoint < target_freq_setting;  | 
 | 52 | +}  | 
 | 53 | + | 
 | 54 | +/**  | 
 | 55 | + * @brief Configure hsfll depending on selected oppoint  | 
 | 56 | + *  | 
 | 57 | + * @param enum oppoint target operation point  | 
 | 58 | + */  | 
 | 59 | +static void ironside_dvfs_configure_hsfll(enum ironside_dvfs_oppoint oppoint)  | 
 | 60 | +{  | 
 | 61 | +	nrf_hsfll_trim_t hsfll_trim = {};  | 
 | 62 | +	uint8_t freq_trim_idx = dvfs_hsfll_data[oppoint].new_f_trim_entry;  | 
 | 63 | + | 
 | 64 | +#if defined(NRF_APPLICATION)  | 
 | 65 | +	hsfll_trim.vsup = NRF_FICR->TRIM.APPLICATION.HSFLL.TRIM.VSUP;  | 
 | 66 | +	hsfll_trim.coarse = NRF_FICR->TRIM.APPLICATION.HSFLL.TRIM.COARSE[freq_trim_idx];  | 
 | 67 | +	hsfll_trim.fine = NRF_FICR->TRIM.APPLICATION.HSFLL.TRIM.FINE[freq_trim_idx];  | 
 | 68 | +#if NRF_HSFLL_HAS_TCOEF_TRIM  | 
 | 69 | +	hsfll_trim.tcoef = NRF_FICR->TRIM.APPLICATION.HSFLL.TRIM.TCOEF;  | 
 | 70 | +#endif  | 
 | 71 | +#else  | 
 | 72 | +#error "Only application core is supported for DVFS"  | 
 | 73 | +#endif  | 
 | 74 | + | 
 | 75 | +	nrf_hsfll_clkctrl_mult_set(NRF_HSFLL, dvfs_hsfll_data[oppoint].new_f_mult);  | 
 | 76 | +	nrf_hsfll_trim_set(NRF_HSFLL, &hsfll_trim);  | 
 | 77 | +	nrf_barrier_w();  | 
 | 78 | + | 
 | 79 | +	nrf_hsfll_task_trigger(NRF_HSFLL, NRF_HSFLL_TASK_FREQ_CHANGE);  | 
 | 80 | +	/* Trigger hsfll task one more time, SEE PAC-4078 */  | 
 | 81 | +	nrf_hsfll_task_trigger(NRF_HSFLL, NRF_HSFLL_TASK_FREQ_CHANGE);  | 
 | 82 | +}  | 
 | 83 | + | 
 | 84 | +/* Function handling steps for DVFS oppoint change. */  | 
 | 85 | +static void ironside_dvfs_prepare_to_scale(enum ironside_dvfs_oppoint dvfs_oppoint)  | 
 | 86 | +{  | 
 | 87 | +	if (ironside_dvfs_is_downscaling(dvfs_oppoint)) {  | 
 | 88 | +		ironside_dvfs_configure_hsfll(dvfs_oppoint);  | 
 | 89 | +	}  | 
 | 90 | +}  | 
 | 91 | + | 
 | 92 | +/* Update MDK variable which is used by nrfx_coredep_delay_us (k_busy_wait). */  | 
 | 93 | +static void ironside_dvfs_update_core_clock(enum ironside_dvfs_oppoint dvfs_oppoint)  | 
 | 94 | +{  | 
 | 95 | +	extern uint32_t SystemCoreClock;  | 
 | 96 | + | 
 | 97 | +	SystemCoreClock = dvfs_hsfll_data[dvfs_oppoint].max_hsfll_freq;  | 
 | 98 | +}  | 
 | 99 | + | 
 | 100 | +/* Perform scaling finnish procedure. */  | 
 | 101 | +static void ironside_dvfs_change_oppoint_complete(enum ironside_dvfs_oppoint dvfs_oppoint)  | 
 | 102 | +{  | 
 | 103 | +	if (!ironside_dvfs_is_downscaling(dvfs_oppoint)) {  | 
 | 104 | +		ironside_dvfs_configure_hsfll(dvfs_oppoint);  | 
 | 105 | +	}  | 
 | 106 | + | 
 | 107 | +	current_dvfs_oppoint = dvfs_oppoint;  | 
 | 108 | +	ironside_dvfs_update_core_clock(dvfs_oppoint);  | 
 | 109 | +}  | 
 | 110 | + | 
 | 111 | +/**  | 
 | 112 | + * @brief Check if ABB analog part is locked.  | 
 | 113 | + *  | 
 | 114 | + * @param abb Pointer to ABB peripheral.  | 
 | 115 | + *  | 
 | 116 | + * @return true if ABB is locked, false otherwise.  | 
 | 117 | + */  | 
 | 118 | +static inline bool ironside_dvfs_is_abb_locked(NRF_ABB_Type *abb)  | 
 | 119 | +{  | 
 | 120 | +	/* Check if ABB analog part is locked. */  | 
 | 121 | +	return ((abb->STATUSANA & ABB_STATUSANA_LOCKED_Msk) != 0);  | 
 | 122 | +}  | 
 | 123 | + | 
 | 124 | +/**  | 
 | 125 | + * @brief Request DVFS oppoint change from IRONside secure domain.  | 
 | 126 | + * This function will send a request over IPC to the IRONside secure domain  | 
 | 127 | + * This function is synchronous and will return when the request is completed.  | 
 | 128 | + *  | 
 | 129 | + * @param oppoint @ref enum ironside_dvfs_oppoint  | 
 | 130 | + * @return int  | 
 | 131 | + */  | 
 | 132 | +static int ironside_dvfs_req_oppoint(enum ironside_dvfs_oppoint oppoint)  | 
 | 133 | +{  | 
 | 134 | +	int err;  | 
 | 135 | + | 
 | 136 | +	struct ironside_call_buf *const buf = ironside_call_alloc();  | 
 | 137 | + | 
 | 138 | +	buf->id = IRONSIDE_CALL_ID_DVFS_SERVICE_V0;  | 
 | 139 | +	buf->args[IRONSIDE_DVFS_SERVICE_OPPOINT_IDX] = oppoint;  | 
 | 140 | + | 
 | 141 | +	ironside_call_dispatch(buf);  | 
 | 142 | + | 
 | 143 | +	if (buf->status == IRONSIDE_CALL_STATUS_RSP_SUCCESS) {  | 
 | 144 | +		err = buf->args[IRONSIDE_DVFS_SERVICE_RETCODE_IDX];  | 
 | 145 | +	} else {  | 
 | 146 | +		err = buf->status;  | 
 | 147 | +	}  | 
 | 148 | + | 
 | 149 | +	ironside_call_release(buf);  | 
 | 150 | + | 
 | 151 | +	return err;  | 
 | 152 | +}  | 
 | 153 | + | 
 | 154 | +int ironside_dvfs_change_oppoint(enum ironside_dvfs_oppoint dvfs_oppoint)  | 
 | 155 | +{  | 
 | 156 | +	int status = 0;  | 
 | 157 | + | 
 | 158 | +	if (!ironside_dvfs_is_oppoint_valid(dvfs_oppoint)) {  | 
 | 159 | +		return -IRONSIDE_DVFS_ERROR_WRONG_OPPOINT;  | 
 | 160 | +	}  | 
 | 161 | + | 
 | 162 | +	if (!ironside_dvfs_is_abb_locked(NRF_ABB)) {  | 
 | 163 | +		return -IRONSIDE_DVFS_ERROR_BUSY;  | 
 | 164 | +	}  | 
 | 165 | + | 
 | 166 | +	if (dvfs_oppoint == current_dvfs_oppoint) {  | 
 | 167 | +		return status;  | 
 | 168 | +	}  | 
 | 169 | + | 
 | 170 | +	if (k_is_in_isr()) {  | 
 | 171 | +		return -IRONSIDE_DVFS_ERROR_ISR_NOT_ALLOWED;  | 
 | 172 | +	}  | 
 | 173 | + | 
 | 174 | +	ironside_dvfs_prepare_to_scale(dvfs_oppoint);  | 
 | 175 | + | 
 | 176 | +	status = ironside_dvfs_req_oppoint(dvfs_oppoint);  | 
 | 177 | + | 
 | 178 | +	if (status != 0) {  | 
 | 179 | +		return status;  | 
 | 180 | +	}  | 
 | 181 | +	ironside_dvfs_change_oppoint_complete(dvfs_oppoint);  | 
 | 182 | + | 
 | 183 | +	return status;  | 
 | 184 | +}  | 
0 commit comments