Skip to content

[nrf fromlist] drivers: clock control: ironside dvfs hsfll #3006

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions drivers/clock_control/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_NRF2_COMMON clock_cont
zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_NRF_FLL16M clock_control_nrf_fll16m.c)
zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_NRF54H_HFXO clock_control_nrf54h_hfxo.c)
zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_NRF_HSFLL_LOCAL clock_control_nrf_hsfll_local.c)
zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_NRF_IRON_HSFLL_LOCAL clock_control_nrf_iron_hsfll_local.c)
zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_NRF_LFCLK clock_control_nrf_lfclk.c)
zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_NRF_AUXPLL clock_control_nrf_auxpll.c)
zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_BOUFFALOLAB_BL60X clock_control_bl60x.c)
Expand Down
20 changes: 20 additions & 0 deletions drivers/clock_control/Kconfig.nrf
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,26 @@ config CLOCK_CONTROL_NRF_HSFLL_LOCAL_NRFS_DVFS_TIMEOUT_MS

endif # CLOCK_CONTROL_NRF_HSFLL_LOCAL

config CLOCK_CONTROL_NRF_IRON_HSFLL_LOCAL
bool "NRF IronSide HSFLL LOCAL driver support"
depends on DT_HAS_NORDIC_NRF_IRON_HSFLL_LOCAL_ENABLED
select NRF_IRONSIDE_DVFS_SERVICE
select CLOCK_CONTROL_NRF2_COMMON
default y

if CLOCK_CONTROL_NRF_IRON_HSFLL_LOCAL

config CLOCK_CONTROL_NRF_IRON_HSFLL_LOCAL_REQ_LOW_FREQ
bool "Local domain scale down after init"
help
Request the lowest operating point after DVFS initialization.

config CLOCK_CONTROL_NRF_IRON_HSFLL_LOCAL_DVFS_TIMEOUT_MS
int "Timeout waiting for dvfs request to complete"
default 2000

endif # CLOCK_CONTROL_NRF_IRON_HSFLL_LOCAL

config CLOCK_CONTROL_NRF_LFCLK
bool "NRF LFCLK driver support"
depends on DT_HAS_NORDIC_NRF_LFCLK_ENABLED
Expand Down
244 changes: 244 additions & 0 deletions drivers/clock_control/clock_control_nrf_iron_hsfll_local.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
* SPDX-License-Identifier: Apache-2.0
*/

#define DT_DRV_COMPAT nordic_nrf_iron_hsfll_local

#include "clock_control_nrf2_common.h"
#include <zephyr/devicetree.h>
#include <zephyr/drivers/clock_control/nrf_clock_control.h>

#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(clock_control_nrf2, CONFIG_CLOCK_CONTROL_LOG_LEVEL);

BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 1, "multiple instances not supported");

#ifdef CONFIG_NRF_IRONSIDE_DVFS_SERVICE
#include <zephyr/drivers/firmware/nrf_ironside/dvfs.h>

#define HSFLL_FREQ_LOW MHZ(64)
#define HSFLL_FREQ_MEDLOW MHZ(128)
#define HSFLL_FREQ_HIGH MHZ(320)

#define IRONSIDE_DVFS_TIMEOUT K_MSEC(CONFIG_CLOCK_CONTROL_NRF_IRON_HSFLL_LOCAL_DVFS_TIMEOUT_MS)

/* Clock options sorted from lowest to highest frequency */
static const struct clock_options {
uint32_t frequency;
enum ironside_dvfs_oppoint setting;
} clock_options[] = {
{
.frequency = HSFLL_FREQ_LOW,
.setting = IRONSIDE_DVFS_OPP_LOW,
},
{
.frequency = HSFLL_FREQ_MEDLOW,
.setting = IRONSIDE_DVFS_OPP_MEDLOW,
},
{
.frequency = HSFLL_FREQ_HIGH,
.setting = IRONSIDE_DVFS_OPP_HIGH,
},
};

struct hsfll_dev_data {
STRUCT_CLOCK_CONFIG(hsfll, ARRAY_SIZE(clock_options)) clk_cfg;
struct k_timer timer;
};

static void hsfll_update_timeout_handler(struct k_timer *timer)
{
struct hsfll_dev_data *dev_data = CONTAINER_OF(timer, struct hsfll_dev_data, timer);

clock_config_update_end(&dev_data->clk_cfg, -ETIMEDOUT);
}

static void hsfll_work_handler(struct k_work *work)
{
struct hsfll_dev_data *dev_data = CONTAINER_OF(work, struct hsfll_dev_data, clk_cfg.work);
enum ironside_dvfs_oppoint required_setting;
uint8_t to_activate_idx;
int rc;

to_activate_idx = clock_config_update_begin(work);
required_setting = clock_options[to_activate_idx].setting;

k_timer_start(&dev_data->timer, IRONSIDE_DVFS_TIMEOUT, K_NO_WAIT);

/* Request the DVFS service to change the OPP point. */
rc = ironside_dvfs_change_oppoint(required_setting);
k_timer_stop(&dev_data->timer);
clock_config_update_end(&dev_data->clk_cfg, rc);
}

static int hsfll_resolve_spec_to_idx(const struct nrf_clock_spec *req_spec)
{
uint32_t req_frequency;

if (req_spec->accuracy || req_spec->precision) {
LOG_ERR("invalid specification of accuracy or precision");
return -EINVAL;
}

req_frequency = req_spec->frequency == NRF_CLOCK_CONTROL_FREQUENCY_MAX
? HSFLL_FREQ_HIGH
: req_spec->frequency;

for (int i = 0; i < ARRAY_SIZE(clock_options); ++i) {
if (req_frequency > clock_options[i].frequency) {
continue;
}

return i;
}

LOG_ERR("invalid frequency");
Copy link
Preview

Copilot AI Jun 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider improving the error message in hsfll_resolve_spec_to_idx to indicate the acceptable frequency range or valid values, which may help integrators diagnose configuration issues.

Suggested change
LOG_ERR("invalid frequency");
LOG_ERR("invalid frequency. Valid frequencies are: %u, %u, %u MHz",
HSFLL_FREQ_LOW / MHZ(1), HSFLL_FREQ_MEDLOW / MHZ(1), HSFLL_FREQ_HIGH / MHZ(1));

Copilot uses AI. Check for mistakes.

return -EINVAL;
}

static void hsfll_get_spec_by_idx(uint8_t idx, struct nrf_clock_spec *spec)
{
spec->frequency = clock_options[idx].frequency;
spec->accuracy = 0;
spec->precision = 0;
}

static struct onoff_manager *hsfll_get_mgr_by_idx(const struct device *dev, uint8_t idx)
{
struct hsfll_dev_data *dev_data = dev->data;

return &dev_data->clk_cfg.onoff[idx].mgr;
}

static struct onoff_manager *hsfll_find_mgr_by_spec(const struct device *dev,
const struct nrf_clock_spec *spec)
{
int idx;

if (!spec) {
return hsfll_get_mgr_by_idx(dev, 0);
}

idx = hsfll_resolve_spec_to_idx(spec);
return idx < 0 ? NULL : hsfll_get_mgr_by_idx(dev, idx);
}
#endif /* CONFIG_NRF_IRONSIDE_DVFS_SERVICE */

static int api_request_hsfll(const struct device *dev, const struct nrf_clock_spec *spec,
struct onoff_client *cli)
{
#ifdef CONFIG_NRF_IRONSIDE_DVFS_SERVICE
struct onoff_manager *mgr = hsfll_find_mgr_by_spec(dev, spec);

if (mgr) {
return clock_config_request(mgr, cli);
}

return -EINVAL;
#else
return -ENOTSUP;
#endif
}

static int api_release_hsfll(const struct device *dev, const struct nrf_clock_spec *spec)
{
#ifdef CONFIG_NRF_IRONSIDE_DVFS_SERVICE
struct onoff_manager *mgr = hsfll_find_mgr_by_spec(dev, spec);

if (mgr) {
return onoff_release(mgr);
}

return -EINVAL;
#else
return -ENOTSUP;
#endif
}

static int api_cancel_or_release_hsfll(const struct device *dev, const struct nrf_clock_spec *spec,
struct onoff_client *cli)
{
#ifdef CONFIG_NRF_IRONSIDE_DVFS_SERVICE
struct onoff_manager *mgr = hsfll_find_mgr_by_spec(dev, spec);

if (mgr) {
return onoff_cancel_or_release(mgr, cli);
}

return -EINVAL;
#else
return -ENOTSUP;
#endif
}

static int api_resolve_hsfll(const struct device *dev, const struct nrf_clock_spec *req_spec,
struct nrf_clock_spec *res_spec)
{
#ifdef CONFIG_NRF_IRONSIDE_DVFS_SERVICE
int idx;

idx = hsfll_resolve_spec_to_idx(req_spec);
if (idx < 0) {
return -EINVAL;
}

hsfll_get_spec_by_idx(idx, res_spec);
return 0;
#else
return -ENOTSUP;
#endif
}

static int hsfll_init(const struct device *dev)
{
#ifdef CONFIG_NRF_IRONSIDE_DVFS_SERVICE
struct hsfll_dev_data *dev_data = dev->data;
int rc;

rc = clock_config_init(&dev_data->clk_cfg, ARRAY_SIZE(dev_data->clk_cfg.onoff),
hsfll_work_handler);
if (rc < 0) {
return rc;
}

k_timer_init(&dev_data->timer, hsfll_update_timeout_handler, NULL);

#endif

return 0;
}

static DEVICE_API(nrf_clock_control, hsfll_drv_api) = {
.std_api = {
.on = api_nosys_on_off,
.off = api_nosys_on_off,
},
.request = api_request_hsfll,

Check notice on line 217 in drivers/clock_control/clock_control_nrf_iron_hsfll_local.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

You may want to run clang-format on this change

drivers/clock_control/clock_control_nrf_iron_hsfll_local.c:217 - .std_api = { - .on = api_nosys_on_off, - .off = api_nosys_on_off, - }, + .std_api = + { + .on = api_nosys_on_off, + .off = api_nosys_on_off, + },
.release = api_release_hsfll,
.cancel_or_release = api_cancel_or_release_hsfll,
.resolve = api_resolve_hsfll,
};

#ifdef CONFIG_NRF_IRONSIDE_DVFS_SERVICE
static struct hsfll_dev_data hsfll_data;
#endif

#ifdef CONFIG_CLOCK_CONTROL_NRF_IRON_HSFLL_LOCAL_REQ_LOW_FREQ
static int dvfs_low_init(void)
{
static const k_timeout_t timeout = IRONSIDE_DVFS_TIMEOUT;
static const struct device *hsfll_dev = DEVICE_DT_GET(DT_CLOCKS_CTLR(DT_NODELABEL(cpu)));
static const struct nrf_clock_spec clk_spec = {.frequency = HSFLL_FREQ_LOW};

return nrf_clock_control_request_sync(hsfll_dev, &clk_spec, timeout);
}

SYS_INIT(dvfs_low_init, APPLICATION, 0);
#endif

DEVICE_DT_INST_DEFINE(0, hsfll_init, NULL,
COND_CODE_1(CONFIG_NRF_IRONSIDE_DVFS_SERVICE,
(&hsfll_data),
(NULL)), NULL, PRE_KERNEL_1,
CONFIG_CLOCK_CONTROL_INIT_PRIORITY, &hsfll_drv_api);
59 changes: 59 additions & 0 deletions dts/bindings/clock/nordic,nrf-iron-hsfll-local.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Copyright (c) 2025 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0

description: |
Nordic nRF local HSFLL with IronSide DVFS support

The local HSFLL mixed-mode IP generates several clock frequencies in the range
from 64 MHz to 400 MHz (in steps of 16 MHz).

Usage example:

hsfll: clock@deadbeef {
compatible = "nordic,nrf-hsfll-local";
reg = <0xdeadbeef 0x1000>;
clocks = <&fll16m>;
clock-frequency = <DT_FREQ_M(320)>;
nordic,ficrs = <&ficr NRF_FICR_TRIM_APPLICATION_HSFLL_TRIM_VSUP>,
<&ficr NRF_FICR_TRIM_APPLICATION_HSFLL_TRIM_COARSE_0>,
<&ficr NRF_FICR_TRIM_APPLICATION_HSFLL_TRIM_FINE_0>;
nordic,ficr-names = "vsup", "coarse", "fine";
};

Required FICR entries are for VSUP, COARSE and FINE trim values.

compatible: "nordic,nrf-iron-hsfll-local"

include: [base.yaml, fixed-clock.yaml, nordic-nrf-ficr-client.yaml]

properties:
reg:
required: true

clocks:
required: true

clock-frequency:
enum:
- 64000000
- 80000000
- 96000000
- 112000000
- 128000000
- 144000000
- 160000000
- 176000000
- 192000000
- 208000000
- 224000000
- 240000000
- 256000000
- 272000000
- 288000000
- 304000000
- 320000000
- 336000000
- 352000000
- 368000000
- 384000000
- 400000000
2 changes: 1 addition & 1 deletion dts/vendor/nordic/nrf54h20.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@
ranges = <0x0 0x52000000 0x1000000>;

cpuapp_hsfll: clock@d000 {
compatible = "nordic,nrf-hsfll-local";
compatible = "nordic,nrf-iron-hsfll-local";
#clock-cells = <0>;
reg = <0xd000 0x1000>;
clocks = <&fll16m>;
Expand Down
Loading