drivers: clock_control: Add support for nRF54H20 clock controllers

Add custom clock_control API for nRF platforms that allows requesting
clocks with specified minimal required attributes (accuracy, precision,
and frequency). Provide an implementation of this API for FLL16M, HFXO,
HSFLL, and LFCLK controllers in the nRF54H20 SoC.

Signed-off-by: Andrzej Głąbek <andrzej.glabek@nordicsemi.no>
Signed-off-by: Bjarki Arge Andreasen <bjarki.andreasen@nordicsemi.no>
This commit is contained in:
Andrzej Głąbek 2024-06-20 13:16:38 +02:00 committed by Anas Nashif
commit 7a2ce2882a
10 changed files with 1419 additions and 1 deletions

View file

@ -36,6 +36,13 @@ zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_AMBIQ clock_cont
zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_PWM clock_control_pwm.c)
zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_RPI_PICO clock_control_rpi_pico.c)
if(CONFIG_CLOCK_CONTROL_NRF2)
zephyr_library_sources(clock_control_nrf2_common.c)
zephyr_library_sources(clock_control_nrf2_fll16m.c)
zephyr_library_sources(clock_control_nrf2_hfxo.c)
zephyr_library_sources(clock_control_nrf2_hsfll.c)
zephyr_library_sources(clock_control_nrf2_lfclk.c)
endif()
if(CONFIG_CLOCK_CONTROL_STM32_CUBE)
zephyr_library_sources_ifdef(CONFIG_CLOCK_STM32_MUX clock_stm32_mux.c)

View file

@ -172,3 +172,24 @@ config CLOCK_CONTROL_NRF_ACCURACY
default 20 if CLOCK_CONTROL_NRF_K32SRC_20PPM
endif # CLOCK_CONTROL_NRF
config CLOCK_CONTROL_NRF2
bool "nRF clock control support"
depends on SOC_SERIES_NRF54HX && !RISCV_CORE_NORDIC_VPR
select ONOFF
select NRFS if HAS_NRFS
imply NRFS_LOCAL_DOMAIN_DVFS_SCALE_DOWN_AFTER_INIT if NRFS_DVFS_LOCAL_DOMAIN
help
Support for nRF clock control devices.
if CLOCK_CONTROL_NRF2
config CLOCK_CONTROL_NRF2_NRFS_DVFS_TIMEOUT_MS
int "Timeout waiting for nrfs dvfs service callback in milliseconds"
default 2000
config CLOCK_CONTROL_NRF2_NRFS_CLOCK_TIMEOUT_MS
int "Timeout waiting for nrfs clock service callback in milliseconds"
default 1000
endif # CLOCK_CONTROL_NRF2

View file

@ -0,0 +1,196 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
* SPDX-License-Identifier: Apache-2.0
*/
#include "clock_control_nrf2_common.h"
#include <hal/nrf_lrcconf.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(clock_control_nrf2, CONFIG_CLOCK_CONTROL_LOG_LEVEL);
#define FLAG_UPDATE_IN_PROGRESS BIT(FLAGS_COMMON_BITS - 1)
#define FLAG_UPDATE_NEEDED BIT(FLAGS_COMMON_BITS - 2)
#define ONOFF_CNT_MAX (FLAGS_COMMON_BITS - 2)
#define CONTAINER_OF_ITEM(ptr, idx, type, array) \
(type *)((char *)ptr - \
(idx * sizeof(array[0])) - \
offsetof(type, array[0]))
/*
* Definition of `struct clock_config_generic`.
* Used to access `clock_config_*` structures in a common way.
*/
STRUCT_CLOCK_CONFIG(generic, ONOFF_CNT_MAX);
static sys_slist_t poweron_main_list;
static struct k_spinlock poweron_main_lock;
static void update_config(struct clock_config_generic *cfg)
{
atomic_val_t prev_flags = atomic_or(&cfg->flags, FLAG_UPDATE_NEEDED);
/* If the update work is already scheduled (FLAG_UPDATE_NEEDED was
* set before the above OR operation) or is currently being executed,
* it is not to be submitted again. In the latter case, it will be
* submitted by clock_config_update_end().
*/
if (prev_flags & (FLAG_UPDATE_NEEDED | FLAG_UPDATE_IN_PROGRESS)) {
return;
}
k_work_submit(&cfg->work);
}
static void onoff_start_option(struct onoff_manager *mgr,
onoff_notify_fn notify)
{
struct clock_onoff *onoff =
CONTAINER_OF(mgr, struct clock_onoff, mgr);
struct clock_config_generic *cfg =
CONTAINER_OF_ITEM(onoff, onoff->idx,
struct clock_config_generic, onoff);
onoff->notify = notify;
(void)atomic_or(&cfg->flags, BIT(onoff->idx));
update_config(cfg);
}
static void onoff_stop_option(struct onoff_manager *mgr,
onoff_notify_fn notify)
{
struct clock_onoff *onoff =
CONTAINER_OF(mgr, struct clock_onoff, mgr);
struct clock_config_generic *cfg =
CONTAINER_OF_ITEM(onoff, onoff->idx,
struct clock_config_generic, onoff);
(void)atomic_and(&cfg->flags, ~BIT(onoff->idx));
update_config(cfg);
notify(mgr, 0);
}
static inline uint8_t get_index_of_highest_bit(uint32_t value)
{
return value ? (uint8_t)(31 - __builtin_clz(value)) : 0;
}
int clock_config_init(void *clk_cfg, uint8_t onoff_cnt, k_work_handler_t update_work_handler)
{
struct clock_config_generic *cfg = clk_cfg;
__ASSERT_NO_MSG(onoff_cnt <= ONOFF_CNT_MAX);
for (int i = 0; i < onoff_cnt; ++i) {
static const struct onoff_transitions transitions = {
.start = onoff_start_option,
.stop = onoff_stop_option
};
int rc;
rc = onoff_manager_init(&cfg->onoff[i].mgr, &transitions);
if (rc < 0) {
return rc;
}
cfg->onoff[i].idx = (uint8_t)i;
}
cfg->onoff_cnt = onoff_cnt;
k_work_init(&cfg->work, update_work_handler);
return 0;
}
uint8_t clock_config_update_begin(struct k_work *work)
{
struct clock_config_generic *cfg =
CONTAINER_OF(work, struct clock_config_generic, work);
uint32_t active_options;
(void)atomic_or(&cfg->flags, FLAG_UPDATE_IN_PROGRESS);
cfg->flags_snapshot = atomic_and(&cfg->flags, ~FLAG_UPDATE_NEEDED);
active_options = cfg->flags_snapshot & BIT_MASK(ONOFF_CNT_MAX);
return get_index_of_highest_bit(active_options);
}
void clock_config_update_end(void *clk_cfg, int status)
{
struct clock_config_generic *cfg = clk_cfg;
atomic_val_t prev_flags;
prev_flags = atomic_and(&cfg->flags, ~FLAG_UPDATE_IN_PROGRESS);
if (!(prev_flags & FLAG_UPDATE_IN_PROGRESS)) {
return;
}
for (int i = 0; i < cfg->onoff_cnt; ++i) {
if (cfg->flags_snapshot & BIT(i)) {
onoff_notify_fn notify = cfg->onoff[i].notify;
if (notify) {
/* If an option was to be activated now
* (it is waiting for a notification) and
* the activation failed, this option's flag
* must be cleared (the option can no longer
* be considered active).
*/
if (status < 0) {
(void)atomic_and(&cfg->flags, ~BIT(i));
}
cfg->onoff[i].notify = NULL;
notify(&cfg->onoff[i].mgr, status);
}
}
}
if (prev_flags & FLAG_UPDATE_NEEDED) {
k_work_submit(&cfg->work);
}
}
int api_nosys_on_off(const struct device *dev, clock_control_subsys_t sys)
{
ARG_UNUSED(dev);
ARG_UNUSED(sys);
return -ENOSYS;
}
void clock_request_lrcconf_poweron_main(struct clock_lrcconf_sink *sink)
{
K_SPINLOCK(&poweron_main_lock) {
if (sys_slist_len(&poweron_main_list) == 0) {
LOG_DBG("%s forced on", "main domain");
NRF_LRCCONF010->POWERON &= ~LRCCONF_POWERON_MAIN_Msk;
NRF_LRCCONF010->POWERON |= LRCCONF_POWERON_MAIN_AlwaysOn;
}
sys_slist_find_and_remove(&poweron_main_list, &sink->node);
sys_slist_append(&poweron_main_list, &sink->node);
}
}
void clock_release_lrcconf_poweron_main(struct clock_lrcconf_sink *sink)
{
K_SPINLOCK(&poweron_main_lock) {
if (!sys_slist_find_and_remove(&poweron_main_list, &sink->node)) {
K_SPINLOCK_BREAK;
}
if (sys_slist_len(&poweron_main_list) > 0) {
K_SPINLOCK_BREAK;
}
LOG_DBG("%s automatic", "main domain");
NRF_LRCCONF010->POWERON &= ~LRCCONF_POWERON_MAIN_Msk;
NRF_LRCCONF010->POWERON |= LRCCONF_POWERON_MAIN_Automatic;
}
}

View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_DRIVERS_CLOCK_CONTROL_NRF2_COMMON_H_
#define ZEPHYR_DRIVERS_CLOCK_CONTROL_NRF2_COMMON_H_
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/onoff.h>
#define FLAGS_COMMON_BITS 10
struct clock_onoff {
struct onoff_manager mgr;
onoff_notify_fn notify;
uint8_t idx;
};
/**
* @brief Defines a type for specific clock configuration structure.
*
* @param type suffix added clock_config_ to form the type name.
* @param _onoff_cnt number of clock configuration options to be handled;
* for each one a separate onoff manager instance is used.
*/
#define STRUCT_CLOCK_CONFIG(type, _onoff_cnt) \
struct clock_config_##type { \
atomic_t flags; \
uint32_t flags_snapshot; \
struct k_work work; \
uint8_t onoff_cnt; \
struct clock_onoff onoff[_onoff_cnt]; \
}
/**
* @brief Initializes a clock configuration structure.
*
* @param clk_cfg pointer to the structure to be initialized.
* @param onoff_cnt number of clock configuration options handled
* handled by the structure.
* @param update_work_handler function that performs configuration update,
* called from the system work queue.
*
* @return 0 on success, negative value when onoff initialization fails.
*/
int clock_config_init(void *clk_cfg, uint8_t onoff_cnt, k_work_handler_t update_work_handler);
/**
* @brief Starts a clock configuration update.
*
* This function is supposed to be called by a specific clock control driver
* from its update work handler.
*
* @param work pointer to the work item received by the update work handler.
*
* @return index of the clock configuration onoff option to be activated.
*/
uint8_t clock_config_update_begin(struct k_work *work);
/**
* @brief Finalizes a clock configuration update.
*
* Notifies all relevant onoff managers about the update result.
* Only the first call after each clock_config_update_begin() performs
* the actual operation. Any further calls are simply no-ops.
*
* @param clk_cfg pointer to the clock configuration structure.
* @param status result to be passed to onoff managers.
*/
void clock_config_update_end(void *clk_cfg, int status);
int api_nosys_on_off(const struct device *dev, clock_control_subsys_t sys);
struct clock_lrcconf_sink {
sys_snode_t node;
};
/**
* @brief Request or release lrcconf main power domain
*/
void clock_request_lrcconf_poweron_main(struct clock_lrcconf_sink *sink);
void clock_release_lrcconf_poweron_main(struct clock_lrcconf_sink *sink);
#endif /* ZEPHYR_DRIVERS_CLOCK_CONTROL_NRF2_COMMON_H_ */

View file

@ -0,0 +1,262 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nordic_nrf_fll16m
#include "clock_control_nrf2_common.h"
#include <zephyr/devicetree.h>
#include <zephyr/drivers/clock_control/nrf_clock_control.h>
#include <hal/nrf_lrcconf.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");
#define FLAG_HFXO_STARTED BIT(FLAGS_COMMON_BITS)
#define FLL16M_MODE_OPEN_LOOP 0
#define FLL16M_MODE_CLOSED_LOOP 1
#define FLL16M_MODE_BYPASS 2
#define FLL16M_MODE_DEFAULT FLL16M_MODE_OPEN_LOOP
#define FLL16M_LFXO_NODE DT_INST_PHANDLE_BY_NAME(0, clocks, lfxo)
#define FLL16M_HFXO_NODE DT_INST_PHANDLE_BY_NAME(0, clocks, hfxo)
#define FLL16M_HAS_LFXO DT_NODE_HAS_STATUS(FLL16M_LFXO_NODE, okay)
#define FLL16M_LFXO_ACCURACY DT_PROP(FLL16M_LFXO_NODE, accuracy_ppm)
#define FLL16M_HFXO_ACCURACY DT_PROP(FLL16M_HFXO_NODE, accuracy_ppm)
#define FLL16M_OPEN_LOOP_ACCURACY DT_INST_PROP(0, open_loop_accuracy_ppm)
#define FLL16M_CLOSED_LOOP_BASE_ACCURACY DT_INST_PROP(0, closed_loop_base_accuracy_ppm)
#define FLL16M_MAX_ACCURACY FLL16M_HFXO_ACCURACY
/* Closed-loop mode uses LFXO as source if present, HFXO otherwise */
#if FLL16M_HAS_LFXO
#define FLL16M_CLOSED_LOOP_ACCURACY (FLL16M_CLOSED_LOOP_BASE_ACCURACY + FLL16M_LFXO_ACCURACY)
#else
#define FLL16M_CLOSED_LOOP_ACCURACY (FLL16M_CLOSED_LOOP_BASE_ACCURACY + FLL16M_HFXO_ACCURACY)
#endif
/* Clock options sorted from lowest to highest accuracy */
static const struct clock_options {
uint16_t accuracy;
uint8_t mode;
} clock_options[] = {
{
.accuracy = FLL16M_OPEN_LOOP_ACCURACY,
.mode = FLL16M_MODE_OPEN_LOOP,
},
{
.accuracy = FLL16M_CLOSED_LOOP_ACCURACY,
.mode = FLL16M_MODE_CLOSED_LOOP,
},
{
/* Bypass mode uses HFXO */
.accuracy = FLL16M_HFXO_ACCURACY,
.mode = FLL16M_MODE_BYPASS,
},
};
struct fll16m_dev_data {
STRUCT_CLOCK_CONFIG(fll16m, ARRAY_SIZE(clock_options)) clk_cfg;
struct onoff_client hfxo_cli;
struct clock_lrcconf_sink lrcconf_sink;
};
struct fll16m_dev_config {
uint32_t fixed_frequency;
};
static void activate_fll16m_mode(struct fll16m_dev_data *dev_data, uint8_t mode)
{
/* TODO: change to nrf_lrcconf_* function when such is available. */
if (mode != FLL16M_MODE_DEFAULT) {
clock_request_lrcconf_poweron_main(&dev_data->lrcconf_sink);
}
NRF_LRCCONF010->CLKCTRL[0].SRC = mode;
if (mode == FLL16M_MODE_DEFAULT) {
clock_release_lrcconf_poweron_main(&dev_data->lrcconf_sink);
}
nrf_lrcconf_task_trigger(NRF_LRCCONF010, NRF_LRCCONF_TASK_CLKSTART_0);
clock_config_update_end(&dev_data->clk_cfg, 0);
}
static void hfxo_cb(struct onoff_manager *mgr,
struct onoff_client *cli,
uint32_t state,
int res)
{
ARG_UNUSED(mgr);
ARG_UNUSED(state);
struct fll16m_dev_data *dev_data =
CONTAINER_OF(cli, struct fll16m_dev_data, hfxo_cli);
if (res < 0) {
clock_config_update_end(&dev_data->clk_cfg, res);
} else {
(void)atomic_or(&dev_data->clk_cfg.flags, FLAG_HFXO_STARTED);
activate_fll16m_mode(dev_data, FLL16M_MODE_BYPASS);
}
}
static void fll16m_work_handler(struct k_work *work)
{
const struct device *hfxo = DEVICE_DT_GET(FLL16M_HFXO_NODE);
struct fll16m_dev_data *dev_data =
CONTAINER_OF(work, struct fll16m_dev_data, clk_cfg.work);
uint8_t to_activate_idx;
to_activate_idx = clock_config_update_begin(work);
if (clock_options[to_activate_idx].mode == FLL16M_MODE_BYPASS) {
int rc;
/* Bypass mode requires HFXO to be running first. */
sys_notify_init_callback(&dev_data->hfxo_cli.notify, hfxo_cb);
rc = nrf_clock_control_request(hfxo, NULL, &dev_data->hfxo_cli);
if (rc < 0) {
clock_config_update_end(&dev_data->clk_cfg, rc);
}
} else {
atomic_val_t prev_flags;
prev_flags = atomic_and(&dev_data->clk_cfg.flags,
~FLAG_HFXO_STARTED);
if (prev_flags & FLAG_HFXO_STARTED) {
(void)nrf_clock_control_release(hfxo, NULL);
}
activate_fll16m_mode(dev_data,
clock_options[to_activate_idx].mode);
}
}
static struct onoff_manager *fll16m_find_mgr(const struct device *dev,
const struct nrf_clock_spec *spec)
{
struct fll16m_dev_data *dev_data = dev->data;
const struct fll16m_dev_config *dev_config = dev->config;
uint16_t accuracy;
if (!spec) {
return &dev_data->clk_cfg.onoff[0].mgr;
}
if (spec->frequency > dev_config->fixed_frequency) {
LOG_ERR("invalid frequency");
return NULL;
}
if (spec->precision) {
LOG_ERR("invalid precision");
return NULL;
}
accuracy = spec->accuracy == NRF_CLOCK_CONTROL_ACCURACY_MAX
? FLL16M_MAX_ACCURACY
: spec->accuracy;
for (int i = 0; i < ARRAY_SIZE(clock_options); ++i) {
if (accuracy &&
accuracy < clock_options[i].accuracy) {
continue;
}
return &dev_data->clk_cfg.onoff[i].mgr;
}
LOG_ERR("invalid accuracy");
return NULL;
}
static int api_request_fll16m(const struct device *dev,
const struct nrf_clock_spec *spec,
struct onoff_client *cli)
{
struct onoff_manager *mgr = fll16m_find_mgr(dev, spec);
if (mgr) {
return onoff_request(mgr, cli);
}
return -EINVAL;
}
static int api_release_fll16m(const struct device *dev,
const struct nrf_clock_spec *spec)
{
struct onoff_manager *mgr = fll16m_find_mgr(dev, spec);
if (mgr) {
return onoff_release(mgr);
}
return -EINVAL;
}
static int api_cancel_or_release_fll16m(const struct device *dev,
const struct nrf_clock_spec *spec,
struct onoff_client *cli)
{
struct onoff_manager *mgr = fll16m_find_mgr(dev, spec);
if (mgr) {
return onoff_cancel_or_release(mgr, cli);
}
return -EINVAL;
}
static int api_get_rate_fll16m(const struct device *dev,
clock_control_subsys_t sys,
uint32_t *rate)
{
ARG_UNUSED(sys);
const struct fll16m_dev_config *dev_config = dev->config;
*rate = dev_config->fixed_frequency;
return 0;
}
static int fll16m_init(const struct device *dev)
{
struct fll16m_dev_data *dev_data = dev->data;
return clock_config_init(&dev_data->clk_cfg,
ARRAY_SIZE(dev_data->clk_cfg.onoff),
fll16m_work_handler);
}
static struct nrf_clock_control_driver_api fll16m_drv_api = {
.std_api = {
.on = api_nosys_on_off,
.off = api_nosys_on_off,
.get_rate = api_get_rate_fll16m,
},
.request = api_request_fll16m,
.release = api_release_fll16m,
.cancel_or_release = api_cancel_or_release_fll16m,
};
static struct fll16m_dev_data fll16m_data;
static const struct fll16m_dev_config fll16m_config = {
.fixed_frequency = DT_INST_PROP(0, clock_frequency),
};
DEVICE_DT_INST_DEFINE(0, fll16m_init, NULL,
&fll16m_data, &fll16m_config,
PRE_KERNEL_1, CONFIG_CLOCK_CONTROL_INIT_PRIORITY,
&fll16m_drv_api);

View file

@ -0,0 +1,196 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nordic_nrf54h_hfxo
#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);
#include <hal/nrf_lrcconf.h>
BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 1,
"multiple instances not supported");
struct dev_data_hfxo {
struct onoff_manager mgr;
onoff_notify_fn notify;
struct k_timer timer;
struct clock_lrcconf_sink lrcconf_sink;
};
struct dev_config_hfxo {
uint32_t fixed_frequency;
uint16_t fixed_accuracy;
k_timeout_t start_up_time;
};
static void hfxo_start_up_timer_handler(struct k_timer *timer)
{
struct dev_data_hfxo *dev_data =
CONTAINER_OF(timer, struct dev_data_hfxo, timer);
/* In specific cases, the HFXOSTARTED event might not be set even
* though the HFXO has started (this is a hardware issue that will
* be fixed). For now, the HFXO is simply assumed to be started
* after its configured start-up time expires.
*/
LOG_DBG("HFXOSTARTED: %u",
nrf_lrcconf_event_check(NRF_LRCCONF010,
NRF_LRCCONF_EVENT_HFXOSTARTED));
if (dev_data->notify) {
dev_data->notify(&dev_data->mgr, 0);
}
}
static void onoff_start_hfxo(struct onoff_manager *mgr, onoff_notify_fn notify)
{
struct dev_data_hfxo *dev_data =
CONTAINER_OF(mgr, struct dev_data_hfxo, mgr);
const struct device *dev = DEVICE_DT_INST_GET(0);
const struct dev_config_hfxo *dev_config = dev->config;
dev_data->notify = notify;
nrf_lrcconf_event_clear(NRF_LRCCONF010, NRF_LRCCONF_EVENT_HFXOSTARTED);
clock_request_lrcconf_poweron_main(&dev_data->lrcconf_sink);
nrf_lrcconf_task_trigger(NRF_LRCCONF010, NRF_LRCCONF_TASK_REQHFXO);
/* Due to a hardware issue, the HFXOSTARTED event is currently
* unreliable. Hence the timer is used to simply wait the expected
* start-up time. To be removed once the hardware is fixed.
*/
k_timer_start(&dev_data->timer, dev_config->start_up_time, K_NO_WAIT);
}
static void onoff_stop_hfxo(struct onoff_manager *mgr, onoff_notify_fn notify)
{
struct dev_data_hfxo *dev_data =
CONTAINER_OF(mgr, struct dev_data_hfxo, mgr);
nrf_lrcconf_task_trigger(NRF_LRCCONF010, NRF_LRCCONF_TASK_STOPREQHFXO);
clock_release_lrcconf_poweron_main(&dev_data->lrcconf_sink);
notify(mgr, 0);
}
static bool is_clock_spec_valid(const struct device *dev,
const struct nrf_clock_spec *spec)
{
const struct dev_config_hfxo *dev_config = dev->config;
if (spec->frequency > dev_config->fixed_frequency) {
LOG_ERR("invalid frequency");
return false;
}
/* Signal an error if an accuracy better than available is requested. */
if (spec->accuracy &&
spec->accuracy != NRF_CLOCK_CONTROL_ACCURACY_MAX &&
spec->accuracy < dev_config->fixed_accuracy) {
LOG_ERR("invalid accuracy");
return false;
}
/* Consider HFXO precision high, skip checking what is requested. */
return true;
}
static int api_request_hfxo(const struct device *dev,
const struct nrf_clock_spec *spec,
struct onoff_client *cli)
{
struct dev_data_hfxo *dev_data = dev->data;
if (spec && !is_clock_spec_valid(dev, spec)) {
return -EINVAL;
}
return onoff_request(&dev_data->mgr, cli);
}
static int api_release_hfxo(const struct device *dev,
const struct nrf_clock_spec *spec)
{
struct dev_data_hfxo *dev_data = dev->data;
if (spec && !is_clock_spec_valid(dev, spec)) {
return -EINVAL;
}
return onoff_release(&dev_data->mgr);
}
static int api_cancel_or_release_hfxo(const struct device *dev,
const struct nrf_clock_spec *spec,
struct onoff_client *cli)
{
struct dev_data_hfxo *dev_data = dev->data;
if (spec && !is_clock_spec_valid(dev, spec)) {
return -EINVAL;
}
return onoff_cancel_or_release(&dev_data->mgr, cli);
}
static int api_get_rate_hfxo(const struct device *dev,
clock_control_subsys_t sys,
uint32_t *rate)
{
ARG_UNUSED(sys);
const struct dev_config_hfxo *dev_config = dev->config;
*rate = dev_config->fixed_frequency;
return 0;
}
static int init_hfxo(const struct device *dev)
{
struct dev_data_hfxo *dev_data = dev->data;
static const struct onoff_transitions transitions = {
.start = onoff_start_hfxo,
.stop = onoff_stop_hfxo
};
int rc;
rc = onoff_manager_init(&dev_data->mgr, &transitions);
if (rc < 0) {
return rc;
}
k_timer_init(&dev_data->timer, hfxo_start_up_timer_handler, NULL);
return 0;
}
static struct nrf_clock_control_driver_api drv_api_hfxo = {
.std_api = {
.on = api_nosys_on_off,
.off = api_nosys_on_off,
.get_rate = api_get_rate_hfxo,
},
.request = api_request_hfxo,
.release = api_release_hfxo,
.cancel_or_release = api_cancel_or_release_hfxo,
};
static struct dev_data_hfxo data_hfxo;
static const struct dev_config_hfxo config_hfxo = {
.fixed_frequency = DT_INST_PROP(0, clock_frequency),
.fixed_accuracy = DT_INST_PROP(0, accuracy_ppm),
.start_up_time = K_USEC(DT_INST_PROP(0, startup_time_us)),
};
DEVICE_DT_INST_DEFINE(0, init_hfxo, NULL,
&data_hfxo, &config_hfxo,
PRE_KERNEL_1, CONFIG_CLOCK_CONTROL_INIT_PRIORITY,
&drv_api_hfxo);

View file

@ -0,0 +1,241 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nordic_nrf_hsfll
#include "clock_control_nrf2_common.h"
#include <zephyr/devicetree.h>
#include <zephyr/drivers/clock_control/nrf_clock_control.h>
#include <hal/nrf_hsfll.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(clock_control_nrf2, CONFIG_CLOCK_CONTROL_LOG_LEVEL);
/* TODO: add support for other HSFLLs */
BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 1,
"multiple instances not supported");
#ifdef CONFIG_NRFS_HAS_DVFS_SERVICE
#include <ld_dvfs_handler.h>
#define FLAG_FREQ_CHANGE_CB_EXPECTED BIT(FLAGS_COMMON_BITS)
#define HSFLL_FREQ_LOW MHZ(64)
#define HSFLL_FREQ_MEDLOW MHZ(128)
#define HSFLL_FREQ_HIGH MHZ(320)
#define NRFS_DVFS_TIMEOUT K_MSEC(CONFIG_CLOCK_CONTROL_NRF2_NRFS_DVFS_TIMEOUT_MS)
/* Clock options sorted from lowest to highest frequency */
static const struct clock_options {
uint32_t frequency;
enum dvfs_frequency_setting setting;
} clock_options[] = {
{
.frequency = HSFLL_FREQ_LOW,
.setting = DVFS_FREQ_LOW,
},
{
.frequency = HSFLL_FREQ_MEDLOW,
.setting = DVFS_FREQ_MEDLOW,
},
{
.frequency = HSFLL_FREQ_HIGH,
.setting = DVFS_FREQ_HIGH,
},
};
struct hsfll_dev_data {
STRUCT_CLOCK_CONFIG(hsfll, ARRAY_SIZE(clock_options)) clk_cfg;
struct k_timer timer;
};
static void freq_setting_applied_cb(enum dvfs_frequency_setting new_setting)
{
ARG_UNUSED(new_setting);
const struct device *dev = DEVICE_DT_INST_GET(0);
struct hsfll_dev_data *dev_data = dev->data;
atomic_val_t prev_flags;
/* Process only expected notifications (after sent requests). */
prev_flags = atomic_and(&dev_data->clk_cfg.flags,
~FLAG_FREQ_CHANGE_CB_EXPECTED);
if (prev_flags & FLAG_FREQ_CHANGE_CB_EXPECTED) {
k_timer_stop(&dev_data->timer);
clock_config_update_end(&dev_data->clk_cfg, 0);
}
}
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 dvfs_frequency_setting 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;
/* Notify the DVFS service about the required setting. */
rc = dvfs_service_handler_change_freq_setting(required_setting);
if (rc < 0) {
clock_config_update_end(&dev_data->clk_cfg, rc);
return;
}
/* And expect a confirmation that the setting is ready to be used. */
(void)atomic_or(&dev_data->clk_cfg.flags, FLAG_FREQ_CHANGE_CB_EXPECTED);
k_timer_start(&dev_data->timer, NRFS_DVFS_TIMEOUT, K_NO_WAIT);
}
static struct onoff_manager *hsfll_find_mgr(const struct device *dev,
const struct nrf_clock_spec *spec)
{
struct hsfll_dev_data *dev_data = dev->data;
uint32_t frequency;
if (!spec) {
return &dev_data->clk_cfg.onoff[0].mgr;
}
if (spec->accuracy || spec->precision) {
LOG_ERR("invalid specification of accuracy or precision");
return NULL;
}
frequency = spec->frequency == NRF_CLOCK_CONTROL_FREQUENCY_MAX
? HSFLL_FREQ_HIGH
: spec->frequency;
for (int i = 0; i < ARRAY_SIZE(clock_options); ++i) {
if (frequency > clock_options[i].frequency) {
continue;
}
return &dev_data->clk_cfg.onoff[i].mgr;
}
LOG_ERR("invalid frequency");
return NULL;
}
#endif /* CONFIG_NRFS_HAS_DVFS_SERVICE */
static int api_request_hsfll(const struct device *dev,
const struct nrf_clock_spec *spec,
struct onoff_client *cli)
{
#ifdef CONFIG_NRFS_HAS_DVFS_SERVICE
struct onoff_manager *mgr = hsfll_find_mgr(dev, spec);
if (mgr) {
return onoff_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_NRFS_HAS_DVFS_SERVICE
struct onoff_manager *mgr = hsfll_find_mgr(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_NRFS_HAS_DVFS_SERVICE
struct onoff_manager *mgr = hsfll_find_mgr(dev, spec);
if (mgr) {
return onoff_cancel_or_release(mgr, cli);
}
return -EINVAL;
#else
return -ENOTSUP;
#endif
}
static int api_get_rate_hsfll(const struct device *dev,
clock_control_subsys_t sys,
uint32_t *rate)
{
ARG_UNUSED(dev);
ARG_UNUSED(sys);
*rate = nrf_hsfll_clkctrl_mult_get(NRF_HSFLL) * MHZ(16);
return 0;
}
static int hsfll_init(const struct device *dev)
{
#ifdef CONFIG_NRFS_HAS_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);
dvfs_service_handler_register_freq_setting_applied_callback(
freq_setting_applied_cb);
#endif
return 0;
}
static struct nrf_clock_control_driver_api hsfll_drv_api = {
.std_api = {
.on = api_nosys_on_off,
.off = api_nosys_on_off,
.get_rate = api_get_rate_hsfll,
},
.request = api_request_hsfll,
.release = api_release_hsfll,
.cancel_or_release = api_cancel_or_release_hsfll,
};
#ifdef CONFIG_NRFS_HAS_DVFS_SERVICE
static struct hsfll_dev_data hsfll_data;
#endif
DEVICE_DT_INST_DEFINE(0, hsfll_init, NULL,
COND_CODE_1(CONFIG_NRFS_HAS_DVFS_SERVICE,
(&hsfll_data),
(NULL)),
NULL,
PRE_KERNEL_1, CONFIG_CLOCK_CONTROL_INIT_PRIORITY,
&hsfll_drv_api);

View file

@ -0,0 +1,264 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nordic_nrf_lfclk
#include "clock_control_nrf2_common.h"
#include <zephyr/devicetree.h>
#include <zephyr/drivers/clock_control/nrf_clock_control.h>
#include <nrfs_clock.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");
#define LFCLK_LFXO_NODE DT_INST_PHANDLE_BY_NAME(0, clocks, lfxo)
#define LFCLK_HFXO_NODE DT_INST_PHANDLE_BY_NAME(0, clocks, hfxo)
#define LFCLK_HAS_LFXO DT_NODE_HAS_STATUS(LFCLK_LFXO_NODE, okay)
#define LFCLK_LFLPRC_ACCURACY DT_INST_PROP(0, lflprc_accuracy_ppm)
#define LFCLK_LFRC_ACCURACY DT_INST_PROP(0, lfrc_accuracy_ppm)
#define LFCLK_LFXO_ACCURACY DT_PROP(LFCLK_LFXO_NODE, accuracy_ppm)
#define LFCLK_HFXO_ACCURACY DT_PROP(LFCLK_HFXO_NODE, accuracy_ppm)
#if LFCLK_HAS_LFXO
#define LFCLK_MAX_ACCURACY LFCLK_LFXO_ACCURACY
#else
#define LFCLK_MAX_ACCURACY LFCLK_HFXO_ACCURACY
#endif
#define NRFS_CLOCK_TIMEOUT K_MSEC(CONFIG_CLOCK_CONTROL_NRF2_NRFS_CLOCK_TIMEOUT_MS)
/* Clock options sorted from lowest to highest accuracy/precision */
static const struct clock_options {
uint16_t accuracy : 15;
uint16_t precision : 1;
nrfs_clock_src_t src;
} clock_options[] = {
{
.accuracy = LFCLK_LFLPRC_ACCURACY,
.precision = 0,
.src = NRFS_CLOCK_SRC_LFCLK_LFLPRC,
},
{
.accuracy = LFCLK_LFRC_ACCURACY,
.precision = 0,
.src = NRFS_CLOCK_SRC_LFCLK_LFRC,
},
{
/* NRFS will request FLL16M use HFXO in bypass mode if SYNTH src is used */
.accuracy = LFCLK_HFXO_ACCURACY,
.precision = 1,
.src = NRFS_CLOCK_SRC_LFCLK_SYNTH,
},
#if LFCLK_HAS_LFXO
#if DT_ENUM_HAS_VALUE(LFCLK_LFXO_NODE, mode, crystal)
{
.accuracy = LFCLK_LFXO_ACCURACY,
.src = NRFS_CLOCK_SRC_LFCLK_XO_PIERCE,
},
{
.accuracy = LFCLK_LFXO_ACCURACY,
.precision = 1,
.src = NRFS_CLOCK_SRC_LFCLK_XO_PIERCE_HP,
},
#elif DT_ENUM_HAS_VALUE(LFCLK_LFXO_NODE, mode, external_sine)
{
.accuracy = LFCLK_LFXO_ACCURACY,
.precision = 0,
.src = NRFS_CLOCK_SRC_LFCLK_XO_EXT_SINE,
},
{
.accuracy = LFCLK_LFXO_ACCURACY,
.precision = 1,
.src = NRFS_CLOCK_SRC_LFCLK_XO_EXT_SINE_HP,
},
#elif DT_ENUM_HAS_VALUE(LFCLK_LFXO_NODE, mode, external_square)
{
.accuracy = LFCLK_LFXO_ACCURACY,
.precision = 0,
.src = NRFS_CLOCK_SRC_LFCLK_XO_EXT_SQUARE,
},
#else
#error "unsupported LFXO mode"
#endif
#endif
};
struct lfclk_dev_data {
STRUCT_CLOCK_CONFIG(lfclk, ARRAY_SIZE(clock_options)) clk_cfg;
struct k_timer timer;
};
struct lfclk_dev_config {
uint32_t fixed_frequency;
};
static void clock_evt_handler(nrfs_clock_evt_t const *p_evt, void *context)
{
struct lfclk_dev_data *dev_data = context;
int status = 0;
k_timer_stop(&dev_data->timer);
if (p_evt->type == NRFS_CLOCK_EVT_REJECT) {
status = -ENXIO;
}
clock_config_update_end(&dev_data->clk_cfg, status);
}
static void lfclk_update_timeout_handler(struct k_timer *timer)
{
struct lfclk_dev_data *dev_data =
CONTAINER_OF(timer, struct lfclk_dev_data, timer);
clock_config_update_end(&dev_data->clk_cfg, -ETIMEDOUT);
}
static void lfclk_work_handler(struct k_work *work)
{
struct lfclk_dev_data *dev_data =
CONTAINER_OF(work, struct lfclk_dev_data, clk_cfg.work);
uint8_t to_activate_idx;
nrfs_err_t err;
to_activate_idx = clock_config_update_begin(work);
err = nrfs_clock_lfclk_src_set(clock_options[to_activate_idx].src,
dev_data);
if (err != NRFS_SUCCESS) {
clock_config_update_end(&dev_data->clk_cfg, -EIO);
} else {
k_timer_start(&dev_data->timer, NRFS_CLOCK_TIMEOUT, K_NO_WAIT);
}
}
static struct onoff_manager *lfclk_find_mgr(const struct device *dev,
const struct nrf_clock_spec *spec)
{
struct lfclk_dev_data *dev_data = dev->data;
const struct lfclk_dev_config *dev_config = dev->config;
uint16_t accuracy;
if (!spec) {
return &dev_data->clk_cfg.onoff[0].mgr;
}
if (spec->frequency > dev_config->fixed_frequency) {
LOG_ERR("invalid frequency");
return NULL;
}
accuracy = spec->accuracy == NRF_CLOCK_CONTROL_ACCURACY_MAX
? LFCLK_MAX_ACCURACY
: spec->accuracy;
for (int i = 0; i < ARRAY_SIZE(clock_options); ++i) {
if ((accuracy &&
accuracy < clock_options[i].accuracy) ||
spec->precision > clock_options[i].precision) {
continue;
}
return &dev_data->clk_cfg.onoff[i].mgr;
}
LOG_ERR("invalid accuracy or precision");
return NULL;
}
static int api_request_lfclk(const struct device *dev,
const struct nrf_clock_spec *spec,
struct onoff_client *cli)
{
struct onoff_manager *mgr = lfclk_find_mgr(dev, spec);
if (mgr) {
return onoff_request(mgr, cli);
}
return -EINVAL;
}
static int api_release_lfclk(const struct device *dev,
const struct nrf_clock_spec *spec)
{
struct onoff_manager *mgr = lfclk_find_mgr(dev, spec);
if (mgr) {
return onoff_release(mgr);
}
return -EINVAL;
}
static int api_cancel_or_release_lfclk(const struct device *dev,
const struct nrf_clock_spec *spec,
struct onoff_client *cli)
{
struct onoff_manager *mgr = lfclk_find_mgr(dev, spec);
if (mgr) {
return onoff_cancel_or_release(mgr, cli);
}
return -EINVAL;
}
static int api_get_rate_lfclk(const struct device *dev,
clock_control_subsys_t sys,
uint32_t *rate)
{
ARG_UNUSED(sys);
const struct lfclk_dev_config *dev_config = dev->config;
*rate = dev_config->fixed_frequency;
return 0;
}
static int lfclk_init(const struct device *dev)
{
struct lfclk_dev_data *dev_data = dev->data;
nrfs_err_t res;
res = nrfs_clock_init(clock_evt_handler);
if (res != NRFS_SUCCESS) {
return -EIO;
}
k_timer_init(&dev_data->timer, lfclk_update_timeout_handler, NULL);
return clock_config_init(&dev_data->clk_cfg,
ARRAY_SIZE(dev_data->clk_cfg.onoff),
lfclk_work_handler);
}
static struct nrf_clock_control_driver_api lfclk_drv_api = {
.std_api = {
.on = api_nosys_on_off,
.off = api_nosys_on_off,
.get_rate = api_get_rate_lfclk,
},
.request = api_request_lfclk,
.release = api_release_lfclk,
.cancel_or_release = api_cancel_or_release_lfclk,
};
static struct lfclk_dev_data lfclk_data;
static const struct lfclk_dev_config lfclk_config = {
.fixed_frequency = DT_INST_PROP(0, clock_frequency),
};
DEVICE_DT_INST_DEFINE(0, lfclk_init, NULL,
&lfclk_data, &lfclk_config,
PRE_KERNEL_1, CONFIG_CLOCK_CONTROL_INIT_PRIORITY,
&lfclk_drv_api);

View file

@ -8,7 +8,9 @@
#define ZEPHYR_INCLUDE_DRIVERS_CLOCK_CONTROL_NRF_CLOCK_CONTROL_H_
#include <zephyr/device.h>
#ifdef NRF_CLOCK
#include <hal/nrf_clock.h>
#endif
#include <zephyr/sys/onoff.h>
#include <zephyr/drivers/clock_control.h>
@ -16,6 +18,8 @@
extern "C" {
#endif
#if defined(CONFIG_CLOCK_CONTROL_NRF)
/** @brief Clocks handled by the CLOCK peripheral.
*
* Enum shall be used as a sys argument in clock_control API.
@ -151,6 +155,145 @@ void z_nrf_clock_bt_ctlr_hf_request(void);
*/
void z_nrf_clock_bt_ctlr_hf_release(void);
#endif /* defined(CONFIG_CLOCK_CONTROL_NRF) */
#if defined(CONFIG_CLOCK_CONTROL_NRF2)
/* Specifies to use the maximum available frequency for a given clock. */
#define NRF_CLOCK_CONTROL_FREQUENCY_MAX UINT32_MAX
/* Specifies to use the maximum available accuracy for a given clock. */
#define NRF_CLOCK_CONTROL_ACCURACY_MAX 1
/* Specifies the required clock accuracy in parts-per-million. */
#define NRF_CLOCK_CONTROL_ACCURACY_PPM(ppm) (ppm)
/* Specifies that high precision of the clock is required. */
#define NRF_CLOCK_CONTROL_PRECISION_HIGH 1
/* Specifies that default precision of the clock is sufficient. */
#define NRF_CLOCK_CONTROL_PRECISION_DEFAULT 0
struct nrf_clock_spec {
uint32_t frequency;
uint16_t accuracy : 15;
uint16_t precision : 1;
};
__subsystem struct nrf_clock_control_driver_api {
struct clock_control_driver_api std_api;
int (*request)(const struct device *dev,
const struct nrf_clock_spec *spec,
struct onoff_client *cli);
int (*release)(const struct device *dev,
const struct nrf_clock_spec *spec);
int (*cancel_or_release)(const struct device *dev,
const struct nrf_clock_spec *spec,
struct onoff_client *cli);
};
/**
* @brief Request a reservation to use a given clock with specified attributes.
*
* The return value indicates the success or failure of an attempt to initiate
* an operation to request the clock be made available. If initiation of the
* operation succeeds, the result of the request operation is provided through
* the configured client notification method, possibly before this call returns.
*
* Note that the call to this function may succeed in a case where the actual
* request fails. Always check the operation completion result.
*
* @param dev pointer to the clock device structure.
* @param spec specification of minimal acceptable attributes, like frequency,
* accuracy, and precision, required for the clock.
* Value of 0 has the meaning of "default" and can be passed
* instead of a given attribute if there is no strict requirement
* in this regard. If there is no specific requirement for any of
* the attributes, this parameter can be NULL.
* @param cli pointer to client state providing instructions on synchronous
* expectations and how to notify the client when the request
* completes. Behavior is undefined if client passes a pointer
* object associated with an incomplete service operation.
*
* @retval non-negative the observed state of the on-off service associated
* with the clock machine at the time the request was
* processed (see onoff_request()), if successful.
* @retval -EIO if service has recorded an error.
* @retval -EINVAL if the function parameters are invalid or the clock
* attributes cannot be provided (e.g. the requested accuracy
* is unavailable).
* @retval -EAGAIN if the reference count would overflow.
*/
static inline
int nrf_clock_control_request(const struct device *dev,
const struct nrf_clock_spec *spec,
struct onoff_client *cli)
{
const struct nrf_clock_control_driver_api *api =
(const struct nrf_clock_control_driver_api *)dev->api;
return api->request(dev, spec, cli);
}
/**
* @brief Release a reserved use of a clock.
*
* @param dev pointer to the clock device structure.
* @param spec the same specification of the clock attributes that was used
* in the reservation request (so that the clock control module
* can keep track of what attributes are still requested).
*
* @retval non-negative the observed state of the on-off service associated
* with the clock machine at the time the request was
* processed (see onoff_release()), if successful.
* @retval -EIO if service has recorded an error.
* @retval -ENOTSUP if the service is not in a state that permits release.
*/
static inline
int nrf_clock_control_release(const struct device *dev,
const struct nrf_clock_spec *spec)
{
const struct nrf_clock_control_driver_api *api =
(const struct nrf_clock_control_driver_api *)dev->api;
return api->release(dev, spec);
}
/**
* @brief Safely cancel a reservation request.
*
* It may be that a client has issued a reservation request but needs to
* shut down before the request has completed. This function attempts to
* cancel the request and issues a release if cancellation fails because
* the request was completed. This synchronously ensures that ownership
* data reverts to the client so is available for a future request.
*
* @param dev pointer to the clock device structure.
* @param spec the same specification of the clock attributes that was used
* in the reservation request.
* @param cli a pointer to the same client state that was provided
* when the operation to be cancelled was issued.
*
* @retval ONOFF_STATE_TO_ON if the cancellation occurred before the transition
* completed.
* @retval ONOFF_STATE_ON if the cancellation occurred after the transition
* completed.
* @retval -EINVAL if the parameters are invalid.
* @retval negative other errors produced by onoff_release().
*/
static inline
int nrf_clock_control_cancel_or_release(const struct device *dev,
const struct nrf_clock_spec *spec,
struct onoff_client *cli)
{
const struct nrf_clock_control_driver_api *api =
(const struct nrf_clock_control_driver_api *)dev->api;
return api->cancel_or_release(dev, spec, cli);
}
#endif /* defined(CONFIG_CLOCK_CONTROL_NRF2) */
#ifdef __cplusplus
}
#endif

View file

@ -11,7 +11,7 @@ rsource "*/Kconfig.defconfig"
if SYS_CLOCK_EXISTS
config CLOCK_CONTROL
default y if (!SOC_SERIES_NRF54HX && !SOC_SERIES_NRF92X)
default y if !SOC_SERIES_NRF92X
endif # SYS_CLOCK_EXISTS