|
| 1 | +/* |
| 2 | + * Copyright (c) 2025 Nordic Semiconductor ASA |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause |
| 5 | + */ |
| 6 | + |
| 7 | +#include <zephyr/kernel.h> |
| 8 | +#include <zephyr/drivers/clock_control.h> |
| 9 | +#include <zephyr/drivers/clock_control/nrf_clock_control.h> |
| 10 | + |
| 11 | +#if defined(CONFIG_CLOCK_CONTROL_NRF) |
| 12 | +#include <nrfx_clock.h> |
| 13 | +#endif /* CONFIG_CLOCK_CONTROL_NRF */ |
| 14 | + |
| 15 | +#include <mpsl_clock.h> |
| 16 | +#include "mpsl_clock_ctrl.h" |
| 17 | + |
| 18 | +/* Variable shared for nrf and nrf2 clock control */ |
| 19 | +static atomic_t m_hfclk_refcnt; |
| 20 | + |
| 21 | +/* Type use to get information about a clock request status */ |
| 22 | +struct clock_onoff_state { |
| 23 | + struct onoff_client cli; |
| 24 | + atomic_t m_clk_ready; |
| 25 | + atomic_t m_clk_refcnt; |
| 26 | + struct k_sem sem; |
| 27 | + int clk_req_rsp; |
| 28 | +}; |
| 29 | + |
| 30 | +static struct clock_onoff_state m_lfclk_state; |
| 31 | + |
| 32 | +static int32_t m_lfclk_release(void); |
| 33 | + |
| 34 | +#define MPSL_LFCLK_REQUEST_WAIT_TIMEOUT_MS 1000 |
| 35 | + |
| 36 | +/** @brief LFCLK request callback. |
| 37 | + * |
| 38 | + * The callback function provided to clock control to notify about LFCLK request being finished. |
| 39 | + */ |
| 40 | +static void lfclk_request_cb(struct onoff_manager *mgr, struct onoff_client *cli, uint32_t state, |
| 41 | + int res) |
| 42 | +{ |
| 43 | + struct clock_onoff_state *clock_state = CONTAINER_OF(cli, struct clock_onoff_state, cli); |
| 44 | + |
| 45 | + clock_state->clk_req_rsp = res; |
| 46 | + k_sem_give(&clock_state->sem); |
| 47 | +} |
| 48 | + |
| 49 | +/** @brief Wait for LFCLK to be ready. |
| 50 | + * |
| 51 | + * The function can time out if there is no response from clock control drvier until |
| 52 | + * MPSL_LFCLK_REQUEST_WAIT_TIMEOUT_MS. |
| 53 | + * |
| 54 | + * @note For nRF54H SoC series waiting for LFCLK can't block the system work queue. The nrf2 clock |
| 55 | + * control driver can return -TIMEDOUT due not handled response from sysctrl. |
| 56 | + * |
| 57 | + * @retval 0 LFCLK is ready. |
| 58 | + * @retval -NRF_EINVAL There were no LFCLK request. |
| 59 | + * @retval -NRF_EFAULT LFCLK request failed. |
| 60 | + */ |
| 61 | +static int32_t m_lfclk_wait(void) |
| 62 | +{ |
| 63 | + int32_t err; |
| 64 | + |
| 65 | + if (atomic_get(&m_lfclk_state.m_clk_ready) == (atomic_val_t) true) { |
| 66 | + return 0; |
| 67 | + } |
| 68 | + |
| 69 | + /* Check if lfclk has been requested */ |
| 70 | + if (atomic_get(&m_lfclk_state.m_clk_refcnt) <= (atomic_val_t)0) { |
| 71 | + return -NRF_EINVAL; |
| 72 | + } |
| 73 | + |
| 74 | + /* Wait for response from clock control */ |
| 75 | + err = k_sem_take(&m_lfclk_state.sem, K_MSEC(MPSL_LFCLK_REQUEST_WAIT_TIMEOUT_MS)); |
| 76 | + if (err < 0) { |
| 77 | + /* Do a gracefull cancel of the request, the function release does this |
| 78 | + * as well as and relase. |
| 79 | + */ |
| 80 | + (void)m_lfclk_release(); |
| 81 | + |
| 82 | + return -NRF_EFAULT; |
| 83 | + } |
| 84 | + |
| 85 | + if (m_lfclk_state.clk_req_rsp < 0) { |
| 86 | + __ASSERT(false, "LFCLK could not be started, reason: %d", |
| 87 | + m_lfclk_state.clk_req_rsp); |
| 88 | + /* Possible failure reasons: |
| 89 | + * # -ERRTIMEDOUT - nRFS service timeout |
| 90 | + * # -EIO - nRFS service error |
| 91 | + * # -ENXIO - request rejected |
| 92 | + * All these mean failure for MPSL. |
| 93 | + */ |
| 94 | + return -NRF_EFAULT; |
| 95 | + } |
| 96 | + |
| 97 | + atomic_set(&m_lfclk_state.m_clk_ready, (atomic_val_t) true); |
| 98 | + |
| 99 | + return 0; |
| 100 | +} |
| 101 | + |
| 102 | +#if defined(CONFIG_CLOCK_CONTROL_NRF) |
| 103 | + |
| 104 | +static void m_lfclk_calibration_start(void) |
| 105 | +{ |
| 106 | + if (IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_DRIVER_CALIBRATION)) { |
| 107 | + z_nrf_clock_calibration_force_start(); |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +static bool m_lfclk_calibration_is_enabled(void) |
| 112 | +{ |
| 113 | + if (IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_DRIVER_CALIBRATION)) { |
| 114 | + return true; |
| 115 | + } else { |
| 116 | + return false; |
| 117 | + } |
| 118 | +} |
| 119 | + |
| 120 | +static int32_t m_lfclk_request(void) |
| 121 | +{ |
| 122 | + struct onoff_manager *mgr = z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_LF); |
| 123 | + int32_t err; |
| 124 | + |
| 125 | + sys_notify_init_callback(&m_lfclk_state.cli.notify, lfclk_request_cb); |
| 126 | + (void)k_sem_init(&m_lfclk_state.sem, 0, 1); |
| 127 | + |
| 128 | + err = onoff_request(mgr, &m_lfclk_state.cli); |
| 129 | + if (err < 0) { |
| 130 | + return err; |
| 131 | + } |
| 132 | + |
| 133 | + atomic_inc(&m_lfclk_state.m_clk_refcnt); |
| 134 | + |
| 135 | + return 0; |
| 136 | +} |
| 137 | + |
| 138 | +static int32_t m_lfclk_release(void) |
| 139 | +{ |
| 140 | + struct onoff_manager *mgr = z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_LF); |
| 141 | + int32_t err; |
| 142 | + |
| 143 | + /* In case there is other ongoing request, cancel it. */ |
| 144 | + err = onoff_cancel_or_release(mgr, &m_lfclk_state.cli); |
| 145 | + if (err < 0) { |
| 146 | + return err; |
| 147 | + } |
| 148 | + |
| 149 | + atomic_dec(&m_lfclk_state.m_clk_refcnt); |
| 150 | + |
| 151 | + return 0; |
| 152 | +} |
| 153 | + |
| 154 | +static void m_hfclk_request(void) |
| 155 | +{ |
| 156 | + /* The z_nrf_clock_bt_ctlr_hf_request doesn't count references to HFCLK, |
| 157 | + * it is caller responsibility handle requests and releases counting. |
| 158 | + */ |
| 159 | + if (atomic_inc(&m_hfclk_refcnt) > (atomic_val_t)0) { |
| 160 | + return; |
| 161 | + } |
| 162 | + |
| 163 | + z_nrf_clock_bt_ctlr_hf_request(); |
| 164 | +} |
| 165 | + |
| 166 | +static void m_hfclk_release(void) |
| 167 | +{ |
| 168 | + /* The z_nrf_clock_bt_ctlr_hf_request doesn't count references to HFCLK, |
| 169 | + * it is caller responsibility to not release the clock if there is |
| 170 | + * other request pending. |
| 171 | + */ |
| 172 | + if (atomic_get(&m_hfclk_refcnt) < (atomic_val_t)1) { |
| 173 | + LOG_WRN("Mismatch between HFCLK request/release"); |
| 174 | + return; |
| 175 | + } |
| 176 | + |
| 177 | + if (atomic_dec(&m_hfclk_refcnt) > (atomic_val_t)1) { |
| 178 | + return; |
| 179 | + } |
| 180 | + |
| 181 | + z_nrf_clock_bt_ctlr_hf_release(); |
| 182 | +} |
| 183 | + |
| 184 | +static bool m_hfclk_is_running(void) |
| 185 | +{ |
| 186 | + if (atomic_get(&m_hfclk_refcnt) > (atomic_val_t)0) { |
| 187 | + nrf_clock_hfclk_t type; |
| 188 | + |
| 189 | + unsigned int key = irq_lock(); |
| 190 | + |
| 191 | + (void)nrfx_clock_is_running(NRF_CLOCK_DOMAIN_HFCLK, &type); |
| 192 | + |
| 193 | + irq_unlock(key); |
| 194 | + |
| 195 | + return ((type == NRF_CLOCK_HFCLK_HIGH_ACCURACY) ? true : false); |
| 196 | + } |
| 197 | + |
| 198 | + return false; |
| 199 | +} |
| 200 | + |
| 201 | +#else |
| 202 | +#error "Unsupported clock control" |
| 203 | +#endif /* CONFIG_CLOCK_CONTROL_NRF */ |
| 204 | + |
| 205 | +static mpsl_clock_lfclk_ctrl_source_t m_nrf_lfclk_ctrl_data = { |
| 206 | + .lfclk_wait = m_lfclk_wait, |
| 207 | + .lfclk_calibration_start = m_lfclk_calibration_start, |
| 208 | + .lfclk_calibration_is_enabled = m_lfclk_calibration_is_enabled, |
| 209 | + .lfclk_request = m_lfclk_request, |
| 210 | + .lfclk_release = m_lfclk_release, |
| 211 | +#if defined(CONFIG_CLOCK_CONTROL_NRF_ACCURACY) |
| 212 | + .accuracy_ppm = CONFIG_CLOCK_CONTROL_NRF_ACCURACY, |
| 213 | +#else |
| 214 | + .accuracy_ppm = MPSL_LFCLK_ACCURACY_PPM, |
| 215 | +#endif /* CONFIG_CLOCK_CONTROL_NRF_ACCURACY */ |
| 216 | + .skip_wait_lfclk_started = IS_ENABLED(CONFIG_SYSTEM_CLOCK_NO_WAIT) |
| 217 | +}; |
| 218 | + |
| 219 | +static mpsl_clock_hfclk_ctrl_source_t m_nrf_hfclk_ctrl_data = { |
| 220 | + .hfclk_request = m_hfclk_request, |
| 221 | + .hfclk_release = m_hfclk_release, |
| 222 | + .hfclk_is_running = m_hfclk_is_running, |
| 223 | + .startup_time_us = CONFIG_MPSL_HFCLK_LATENCY |
| 224 | +}; |
| 225 | + |
| 226 | +int32_t mpsl_clock_ctrl_init(void) |
| 227 | +{ |
| 228 | + return mpsl_clock_ctrl_source_register(&m_nrf_lfclk_ctrl_data, &m_nrf_hfclk_ctrl_data); |
| 229 | +} |
| 230 | + |
| 231 | +int32_t mpsl_clock_ctrl_uninit(void) |
| 232 | +{ |
| 233 | + return mpsl_clock_ctrl_source_unregister(); |
| 234 | +} |
0 commit comments