| /* |
| * Copyright (c) 2019 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #include <zephyr/drivers/sensor.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include "nrf_clock_calibration.h" |
| #include <zephyr/drivers/clock_control/nrf_clock_control.h> |
| #include <nrfx_clock.h> |
| #include <zephyr/logging/log.h> |
| #include <stdlib.h> |
| |
| LOG_MODULE_DECLARE(clock_control, CONFIG_CLOCK_CONTROL_LOG_LEVEL); |
| |
| /** |
| * Terms: |
| * - calibration - overall process of LFRC clock calibration which is performed |
| * periodically, calibration may include temperature monitoring, hf XTAL |
| * starting and stopping. |
| * - cycle - all calibration phases (waiting, temperature monitoring, |
| * calibration). |
| * - process - calibration process which may consists of hf XTAL clock |
| * requesting, performing hw calibration and releasing hf clock. |
| * - hw_cal - calibration action performed by the hardware. |
| * |
| * Those terms are later on used in function names. |
| * |
| * In order to ensure that low frequency clock is not released when calibration |
| * is ongoing, it is requested by the calibration process and released when |
| * calibration is done. |
| */ |
| |
| static atomic_t cal_process_in_progress; |
| static uint8_t calib_skip_cnt; /* Counting down skipped calibrations. */ |
| static volatile int total_cnt; /* Total number of calibrations. */ |
| static volatile int total_skips_cnt; /* Total number of skipped calibrations. */ |
| |
| |
| static void cal_hf_callback(struct onoff_manager *mgr, |
| struct onoff_client *cli, |
| uint32_t state, int res); |
| static void cal_lf_callback(struct onoff_manager *mgr, |
| struct onoff_client *cli, |
| uint32_t state, int res); |
| |
| static struct onoff_client client; |
| static struct onoff_manager *mgrs; |
| |
| /* Temperature sensor is only needed if |
| * CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP > 0, since a value of 0 |
| * indicates performing calibration periodically regardless of temperature |
| * change. |
| */ |
| #define USE_TEMP_SENSOR \ |
| (CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP > 0) |
| |
| #if USE_TEMP_SENSOR |
| static const struct device *const temp_sensor = |
| DEVICE_DT_GET_OR_NULL(DT_INST(0, nordic_nrf_temp)); |
| |
| static void measure_temperature(struct k_work *work); |
| static K_WORK_DEFINE(temp_measure_work, measure_temperature); |
| static int16_t prev_temperature; /* Previous temperature measurement. */ |
| #endif /* USE_TEMP_SENSOR */ |
| |
| static void timeout_handler(struct k_timer *timer); |
| static K_TIMER_DEFINE(backoff_timer, timeout_handler, NULL); |
| |
| static void clk_request(struct onoff_manager *mgr, struct onoff_client *cli, |
| onoff_client_callback callback) |
| { |
| int err; |
| |
| sys_notify_init_callback(&cli->notify, callback); |
| err = onoff_request(mgr, cli); |
| __ASSERT_NO_MSG(err >= 0); |
| } |
| |
| static void clk_release(struct onoff_manager *mgr) |
| { |
| int err; |
| |
| err = onoff_release(mgr); |
| __ASSERT_NO_MSG(err >= 0); |
| } |
| |
| static void hf_request(void) |
| { |
| clk_request(&mgrs[CLOCK_CONTROL_NRF_TYPE_HFCLK], &client, cal_hf_callback); |
| } |
| |
| static void lf_request(void) |
| { |
| clk_request(&mgrs[CLOCK_CONTROL_NRF_TYPE_LFCLK], &client, cal_lf_callback); |
| } |
| |
| static void hf_release(void) |
| { |
| clk_release(&mgrs[CLOCK_CONTROL_NRF_TYPE_HFCLK]); |
| } |
| |
| static void lf_release(void) |
| { |
| clk_release(&mgrs[CLOCK_CONTROL_NRF_TYPE_LFCLK]); |
| } |
| |
| static void cal_lf_callback(struct onoff_manager *mgr, |
| struct onoff_client *cli, |
| uint32_t state, int res) |
| { |
| hf_request(); |
| } |
| |
| /* Start actual HW calibration assuming that HFCLK XTAL is on. */ |
| static void start_hw_cal(void) |
| { |
| nrfx_clock_calibration_start(); |
| calib_skip_cnt = CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP; |
| } |
| |
| /* Start cycle by starting backoff timer and releasing HFCLK XTAL. */ |
| static void start_cycle(void) |
| { |
| k_timer_start(&backoff_timer, |
| K_MSEC(CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD), |
| K_NO_WAIT); |
| hf_release(); |
| |
| if (!IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_LF_ALWAYS_ON)) { |
| lf_release(); |
| } |
| |
| cal_process_in_progress = 0; |
| } |
| |
| static void start_cal_process(void) |
| { |
| if (atomic_cas(&cal_process_in_progress, 0, 1) == false) { |
| return; |
| } |
| |
| if (IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_LF_ALWAYS_ON)) { |
| hf_request(); |
| } else { |
| /* LF clock is probably running but it is requested to ensure |
| * that it is not released while calibration process in ongoing. |
| * If system releases the clock during calibration process it |
| * will be released at the end of calibration process and |
| * stopped in consequence. |
| */ |
| lf_request(); |
| } |
| } |
| |
| static void timeout_handler(struct k_timer *timer) |
| { |
| start_cal_process(); |
| } |
| |
| /* Called when HFCLK XTAL is on. Schedules temperature measurement or triggers |
| * calibration. |
| */ |
| static void cal_hf_callback(struct onoff_manager *mgr, |
| struct onoff_client *cli, |
| uint32_t state, int res) |
| { |
| #if USE_TEMP_SENSOR |
| if (!device_is_ready(temp_sensor)) { |
| start_hw_cal(); |
| } else { |
| k_work_submit(&temp_measure_work); |
| } |
| #else |
| start_hw_cal(); |
| #endif /* USE_TEMP_SENSOR */ |
| } |
| |
| #if USE_TEMP_SENSOR |
| /* Convert sensor value to 0.25'C units. */ |
| static inline int16_t sensor_value_to_temp_unit(struct sensor_value *val) |
| { |
| return (int16_t)(4 * val->val1 + val->val2 / 250000); |
| } |
| |
| /* Function reads from temperature sensor and converts to 0.25'C units. */ |
| static int get_temperature(int16_t *tvp) |
| { |
| struct sensor_value sensor_val; |
| int rc = sensor_sample_fetch(temp_sensor); |
| |
| if (rc == 0) { |
| rc = sensor_channel_get(temp_sensor, SENSOR_CHAN_DIE_TEMP, |
| &sensor_val); |
| } |
| if (rc == 0) { |
| *tvp = sensor_value_to_temp_unit(&sensor_val); |
| } |
| return rc; |
| } |
| |
| /* Function determines if calibration should be performed based on temperature |
| * measurement. Function is called from system work queue context. It is |
| * reading temperature from TEMP sensor and compares with last measurement. |
| */ |
| static void measure_temperature(struct k_work *work) |
| { |
| int16_t temperature = 0; |
| int16_t diff = 0; |
| bool started = false; |
| int rc; |
| |
| rc = get_temperature(&temperature); |
| |
| if (rc != 0) { |
| /* Temperature read failed, force calibration. */ |
| calib_skip_cnt = 0; |
| } else { |
| diff = abs(temperature - prev_temperature); |
| } |
| |
| if ((calib_skip_cnt == 0) || |
| (diff >= CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_TEMP_DIFF)) { |
| prev_temperature = temperature; |
| started = true; |
| start_hw_cal(); |
| } else { |
| calib_skip_cnt--; |
| total_skips_cnt++; |
| start_cycle(); |
| } |
| |
| LOG_DBG("Calibration %s. Temperature diff: %d (in 0.25'C units).", |
| started ? "started" : "skipped", diff); |
| } |
| #endif /* USE_TEMP_SENSOR */ |
| |
| void z_nrf_clock_calibration_init(struct onoff_manager *onoff_mgrs) |
| { |
| mgrs = onoff_mgrs; |
| total_cnt = 0; |
| total_skips_cnt = 0; |
| } |
| |
| static void start_unconditional_cal_process(void) |
| { |
| calib_skip_cnt = 0; |
| start_cal_process(); |
| } |
| |
| void z_nrf_clock_calibration_force_start(void) |
| { |
| /* if it's already in progress that is good enough. */ |
| if (cal_process_in_progress) { |
| return; |
| } |
| |
| start_unconditional_cal_process(); |
| } |
| |
| void z_nrf_clock_calibration_lfclk_started(void) |
| { |
| start_unconditional_cal_process(); |
| } |
| |
| void z_nrf_clock_calibration_lfclk_stopped(void) |
| { |
| k_timer_stop(&backoff_timer); |
| LOG_DBG("Calibration stopped"); |
| } |
| |
| void z_nrf_clock_calibration_done_handler(void) |
| { |
| total_cnt++; |
| LOG_DBG("Calibration done."); |
| |
| start_cycle(); |
| } |
| |
| int z_nrf_clock_calibration_count(void) |
| { |
| if (!IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_DEBUG)) { |
| return -1; |
| } |
| |
| return total_cnt; |
| } |
| |
| int z_nrf_clock_calibration_skips_count(void) |
| { |
| if (!IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_DEBUG)) { |
| return -1; |
| } |
| |
| return total_skips_cnt; |
| } |