| /* |
| * 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); |
| |
| /** Temperature sensor DT node */ |
| #define TEMP_NODE DT_INST(0, nordic_nrf_temp) |
| |
| /** |
| * 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 int16_t prev_temperature; /* Previous temperature measurement. */ |
| 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 cli; |
| static struct onoff_manager *mgrs; |
| |
| static const struct device *temp_sensor; |
| |
| static void measure_temperature(struct k_work *work); |
| static K_WORK_DEFINE(temp_measure_work, measure_temperature); |
| |
| 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], &cli, cal_hf_callback); |
| } |
| |
| static void lf_request(void) |
| { |
| clk_request(&mgrs[CLOCK_CONTROL_NRF_TYPE_LFCLK], &cli, 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 ((temp_sensor == NULL) || !IS_ENABLED(CONFIG_MULTITHREADING)) { |
| start_hw_cal(); |
| } else { |
| k_work_submit(&temp_measure_work); |
| } |
| } |
| |
| /* 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); |
| } |
| |
| void z_nrf_clock_calibration_init(struct onoff_manager *onoff_mgrs) |
| { |
| mgrs = onoff_mgrs; |
| total_cnt = 0; |
| total_skips_cnt = 0; |
| } |
| |
| #if CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP |
| static int temp_sensor_init(const struct device *arg) |
| { |
| temp_sensor = DEVICE_DT_GET_OR_NULL(TEMP_NODE); |
| if ((temp_sensor != NULL) && !device_is_ready(temp_sensor)) { |
| LOG_ERR("Temperature sensor not ready"); |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| SYS_INIT(temp_sensor_init, APPLICATION, 0); |
| #endif /* CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP */ |
| |
| 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; |
| } |