Skip to content

Commit

Permalink
mpsl: clock_control: Integration layer with nRF clock control
Browse files Browse the repository at this point in the history
In the past MPSL used its own implementation of clock control.
The approach changes due to lack of acces to clocks from Radio
core in nRF54h20 SoC. Thaking this opportunity we provide
experimental support for external clock control to nRF52 and nRF53
SoC Series.

The new module is an integration layer between new MPSL API that
allows for registration of external clock driver and nRF clock
control driver. The implementation in this commit provides
integration with MPSL for nrf clock control for nRF52 and nRF53
SoC series.

Note: The support for nRF52 and nRF53 SoC series is experimental
and is not enabled by default.

Use of nrf clock control with MPSL allows to initialize the
library in POST_KERNEL stage. Thanks to that we can use
kernel synchronization primitives and non blocking waits.

The change in MPSL init level and priority affects BT_LL_SOFTDEVIDE-
_HCI_SYS_INIT_PRIORITY. The HCI driver for SDC depends on MPSL so
it must be initialized after the MPSL.

Signed-off-by: Piotr Pryga <[email protected]>
  • Loading branch information
ppryga-nordic committed Feb 7, 2025
1 parent 9b0572b commit 0c90a1b
Show file tree
Hide file tree
Showing 11 changed files with 377 additions and 26 deletions.
4 changes: 3 additions & 1 deletion drivers/mpsl/clock_control/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ config CLOCK_CONTROL_MPSL
bool
depends on MPSL
depends on CLOCK_CONTROL
depends on !SOC_SERIES_NRF54HX
depends on !MPSL_USE_EXTERNAL_CLOCK_CONTROL
default y
select CLOCK_CONTROL_NRF_FORCE_ALT
help
Use the clock driver provided as part of the MPSL library.
8 changes: 8 additions & 0 deletions subsys/bluetooth/controller/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -618,5 +618,13 @@ config BT_CTLR_SDC_LE_POWER_CLASS_1
See Bluetooth Core Specification, Vol 6, Part A, Section 3
Transmitter Characteristics for more information.

config BT_LL_SOFTDEVICE_INIT_PRIORITY
int
default 53 if MPSL_USE_EXTERNAL_CLOCK_CONTROL
default KERNEL_INIT_PRIORITY_DEFAULT
help
This option configures the LL_SOFTDEVICE initialization priority level. The priority
must be lower than the CONFIG_MPSL_INIT_PRIORITY due to dependency on the MPSL library.

endmenu
endif # BT_LL_SOFTDEVICE
7 changes: 6 additions & 1 deletion subsys/bluetooth/controller/hci_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -1523,10 +1523,15 @@ static int hci_driver_init(const struct device *dev)
return err;
}

#if defined(CONFIG_MPSL_USE_EXTERNAL_CLOCK_CONTROL)
BUILD_ASSERT(CONFIG_BT_LL_SOFTDEVICE_INIT_PRIORITY > CONFIG_MPSL_INIT_PRIORITY,
"MPSL must be initialized before Soft Device Controller");
#endif /* CONFIG_MPSL_USE_EXTERNAL_CLOCK_CONTROL */

#define BT_HCI_CONTROLLER_INIT(inst) \
static struct hci_driver_data data_##inst; \
DEVICE_DT_INST_DEFINE(inst, hci_driver_init, NULL, &data_##inst, NULL, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &hci_driver_api)
CONFIG_BT_LL_SOFTDEVICE_INIT_PRIORITY, &hci_driver_api)

/* Only a single instance is supported */
BT_HCI_CONTROLLER_INIT(0)
4 changes: 4 additions & 0 deletions subsys/mpsl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ if (CONFIG_MPSL_USE_ZEPHYR_PM)
add_subdirectory(pm)
endif()

if (CONFIG_MPSL_USE_EXTERNAL_CLOCK_CONTROL)
add_subdirectory(clock_ctrl)
endif()

add_subdirectory_ifdef(CONFIG_MPSL_PIN_DEBUG pin_debug)
1 change: 1 addition & 0 deletions subsys/mpsl/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ rsource "cx/Kconfig"
rsource "init/Kconfig"
rsource "pin_debug/Kconfig"
rsource "pm/Kconfig"
rsource "clock_ctrl/Kconfig"

endif # !MPSL_FEM_ONLY

Expand Down
9 changes: 9 additions & 0 deletions subsys/mpsl/clock_ctrl/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#
# Copyright (c) 2025 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

zephyr_library()

zephyr_library_sources(mpsl_clock_ctrl.c)
15 changes: 15 additions & 0 deletions subsys/mpsl/clock_ctrl/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#
# Copyright (c) 2025 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

config MPSL_USE_EXTERNAL_CLOCK_CONTROL
bool "Use external clock control [EXPERIMENTAL]"
depends on MPSL
select CLOCK_CONTROL
default y if SOC_SERIES_NRF54HX
select EXPERIMENTAL if SOC_SERIES_NRF52X || SOC_SERIES_NRF53X
help
This option configures MPSL to use an external clock driver, and
not the clock driver provided as part of the MPSL library.
233 changes: 233 additions & 0 deletions subsys/mpsl/clock_ctrl/mpsl_clock_ctrl.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/

#include <zephyr/kernel.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/nrf_clock_control.h>

#if defined(CONFIG_CLOCK_CONTROL_NRF)
#include <nrfx_clock.h>
#endif /* CONFIG_CLOCK_CONTROL_NRF */

#include <mpsl_clock.h>
#include "mpsl_clock_ctrl.h"

/* Variable shared for nrf and nrf2 clock control */
static atomic_t m_hfclk_refcnt;

/* Type use to get information about a clock request status */
struct clock_onoff_state {
struct onoff_client cli;
atomic_t m_clk_ready;
atomic_t m_clk_refcnt;
struct k_sem sem;
int clk_req_rsp;
};

static struct clock_onoff_state m_lfclk_state;

static int32_t m_lfclk_release(void);

#define MPSL_LFCLK_REQUEST_WAIT_TIMEOUT_MS 1000

/** @brief LFCLK request callback.
*
* The callback function provided to clock control to notify about LFCLK request being finished.
*/
static void lfclk_request_cb(struct onoff_manager *mgr, struct onoff_client *cli, uint32_t state,
int res)
{
struct clock_onoff_state *clock_state = CONTAINER_OF(cli, struct clock_onoff_state, cli);

clock_state->clk_req_rsp = res;
k_sem_give(&clock_state->sem);
}

/** @brief Wait for LFCLK to be ready.
*
* The function can time out if there is no response from clock control drvier until
* MPSL_LFCLK_REQUEST_WAIT_TIMEOUT_MS.
*
* @note For nRF54H SoC series waiting for LFCLK can't block the system work queue. The nrf2 clock
* control driver can return -TIMEDOUT due not handled response from sysctrl.
*
* @retval 0 LFCLK is ready.
* @retval -NRF_EINVAL There were no LFCLK request.
* @retval -NRF_EFAULT LFCLK request failed.
*/
static int32_t m_lfclk_wait(void)
{
int32_t err;

if (atomic_get(&m_lfclk_state.m_clk_ready) == (atomic_val_t) true) {
return 0;
}

/* Check if lfclk has been requested */
if (atomic_get(&m_lfclk_state.m_clk_refcnt) <= (atomic_val_t)0) {
return -NRF_EINVAL;
}

/* Wait for response from clock control */
err = k_sem_take(&m_lfclk_state.sem, K_MSEC(MPSL_LFCLK_REQUEST_WAIT_TIMEOUT_MS));
if (err < 0) {
/* Do a gracefull cancel of the request, the function release does this
* as well as and relase.
*/
(void)m_lfclk_release();

return -NRF_EFAULT;
}

if (m_lfclk_state.clk_req_rsp < 0) {
__ASSERT(false, "LFCLK could not be started, reason: %d",
m_lfclk_state.clk_req_rsp);
/* Possible failure reasons:
* # -ERRTIMEDOUT - nRFS service timeout
* # -EIO - nRFS service error
* # -ENXIO - request rejected
* All these mean failure for MPLS.
*/
return -NRF_EFAULT;
}

atomic_set(&m_lfclk_state.m_clk_ready, (atomic_val_t) true);

return 0;
}

#if defined(CONFIG_CLOCK_CONTROL_NRF)

static void m_lfclk_calibration_start(void)
{
if (IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_DRIVER_CALIBRATION)) {
z_nrf_clock_calibration_force_start();
}
}

static bool m_lfclk_calibration_is_enabled(void)
{
if (IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_DRIVER_CALIBRATION)) {
return true;
} else {
return false;
}
}

static int32_t m_lfclk_request(void)
{
struct onoff_manager *mgr = z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_LF);
int32_t err;

sys_notify_init_callback(&m_lfclk_state.cli.notify, lfclk_request_cb);
(void)k_sem_init(&m_lfclk_state.sem, 0, 1);

err = onoff_request(mgr, &m_lfclk_state.cli);
if (err < 0) {
return err;
}

atomic_inc(&m_lfclk_state.m_clk_refcnt);

return 0;
}

static int32_t m_lfclk_release(void)
{
struct onoff_manager *mgr = z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_LF);
int32_t err;

/* In case there is other ongoing request, cancel it. */
err = onoff_cancel_or_release(mgr, &m_lfclk_state.cli);
if (err < 0) {
return err;
}

atomic_dec(&m_lfclk_state.m_clk_refcnt);

return 0;
}

static void m_hfclk_request(void)
{
/* The z_nrf_clock_bt_ctlr_hf_request doesn't count references to HFCLK,
* it is caller responsibility handle requests and releases counting.
*/
if (atomic_inc(&m_hfclk_refcnt) > (atomic_val_t)0) {
return;
}

z_nrf_clock_bt_ctlr_hf_request();
}

static void m_hfclk_release(void)
{
/* The z_nrf_clock_bt_ctlr_hf_request doesn't count references to HFCLK,
* it is caller responsibility to not release the clock if there is
* other request pending.
*/
if (atomic_get(&m_hfclk_refcnt) < (atomic_val_t)1) {
return;
}

if (atomic_dec(&m_hfclk_refcnt) > (atomic_val_t)1) {
return;
}

z_nrf_clock_bt_ctlr_hf_release();
}

static bool m_hfclk_is_running(void)
{
if (atomic_get(&m_hfclk_refcnt) > (atomic_val_t)0) {
nrf_clock_hfclk_t type;

unsigned int key = irq_lock();

(void)nrfx_clock_is_running(NRF_CLOCK_DOMAIN_HFCLK, &type);

irq_unlock(key);

return ((type == NRF_CLOCK_HFCLK_HIGH_ACCURACY) ? true : false);
}

return false;
}

#else
#error "Unsupported clock control"
#endif /* CONFIG_CLOCK_CONTROL_NRF */

static mpsl_clock_lfclk_ctrl_source_t m_nrf_lfclk_ctrl_data = {
.lfclk_wait = m_lfclk_wait,
.lfclk_calibration_start = m_lfclk_calibration_start,
.lfclk_calibration_is_enabled = m_lfclk_calibration_is_enabled,
.lfclk_request = m_lfclk_request,
.lfclk_release = m_lfclk_release,
#if defined(CONFIG_CLOCK_CONTROL_NRF_ACCURACY)
.accuracy_ppm = CONFIG_CLOCK_CONTROL_NRF_ACCURACY,
#else
.accuracy_ppm = MPSL_LFCLK_ACCURACY_PPM,
#endif /* CONFIG_CLOCK_CONTROL_NRF_ACCURACY */
.skip_wait_lfclk_started = IS_ENABLED(CONFIG_SYSTEM_CLOCK_NO_WAIT)
};

static mpsl_clock_hfclk_ctrl_source_t m_nrf_hfclk_ctrl_data = {
.hfclk_request = m_hfclk_request,
.hfclk_release = m_hfclk_release,
.hfclk_is_running = m_hfclk_is_running,
.startup_time_us = CONFIG_MPSL_HFCLK_LATENCY
};

int32_t mpsl_clock_ctrl_init(void)
{
return mpsl_clock_ctrl_source_register(&m_nrf_lfclk_ctrl_data, &m_nrf_hfclk_ctrl_data);
}

int32_t mpsl_clock_ctrl_uninit(void)
{
return mpsl_clock_ctrl_source_unregister();
}
33 changes: 33 additions & 0 deletions subsys/mpsl/clock_ctrl/mpsl_clock_ctrl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/

#ifndef MPSL_CLOCK_CTRL_H__
#define MPSL_CLOCK_CTRL_H__

#ifdef __cplusplus
extern "C" {
#endif

/** @brief Initialize MPSL integration with NRF clock control
*
* @retval 0 MPSL clock integration initialized successfully.
* @retval -NRF_EPERM Clock control is already initialized.
* @retval -NRF_EINVAL Invalid parameters supplied.
*/
int32_t mpsl_clock_ctrl_init(void);

/** @brief Uninitialize MPSL integration with NRF clock control
*
* @retval 0 MPSL clock was uninitialized successfully.
* @retval -NRF_EPERM MPSL was not initialized before the call.
*/
int32_t mpsl_clock_ctrl_uninit(void);

#ifdef __cplusplus
}
#endif

#endif /* MPSL_CLOCK_CTRL_H__ */
12 changes: 11 additions & 1 deletion subsys/mpsl/init/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ config MPSL_DYNAMIC_INTERRUPTS
config MPSL_TRIGGER_IPC_TASK_ON_RTC_START
bool "Trigger an IPC task when the RTC starts"
depends on SOC_NRF5340_CPUNET
depends on CLOCK_CONTROL_MPSL
help
This option configures MPSL to trigger an IPC task at the time the
RTC starts. This can be used for synchronizing time betwen the
Expand Down Expand Up @@ -98,13 +99,22 @@ config MPSL_HFCLK_LATENCY

config MPSL_CALIBRATION_PERIOD
int "Calibration callback period in milliseconds"
depends on (SOC_SERIES_NRF54LX || CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION) && !SOC_SERIES_NRF54HX
depends on CLOCK_CONTROL_MPSL && (SOC_SERIES_NRF54LX || CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION)
default CLOCK_CONTROL_NRF_CALIBRATION_PERIOD if CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION
default 4000
help
This configuration means how often the calibration callback to mpsl is called.
On 54L, this still needs to be called even if LFRC is not used.

config MPSL_INIT_PRIORITY
int
default 52 if MPSL_USE_EXTERNAL_CLOCK_CONTROL
default KERNEL_INIT_PRIORITY_DEFAULT
help
This option configures MPSL system init priority level. For nRF54H SoC series the priority
must be lower than CONFIG_NRFS_BACKEND_IPC_SERVICE_INIT_PRIO. The nrf2 clock control depends
on the nRFS backend.

module=MPSL
module-str=MPSL
source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config"
Loading

0 comments on commit 0c90a1b

Please sign in to comment.