You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
306 lines
7.4 KiB
306 lines
7.4 KiB
/* |
|
* Copyright (c) 2019 Nordic Semiconductor ASA |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
#include <drivers/sensor.h> |
|
#include <drivers/clock_control.h> |
|
#include "nrf_clock_calibration.h" |
|
#include <drivers/clock_control/nrf_clock_control.h> |
|
#include <nrfx_clock.h> |
|
#include <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 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) |
|
{ |
|
/* Workaround for Errata 192 */ |
|
if (IS_ENABLED(CONFIG_SOC_SERIES_NRF52X)) { |
|
*(volatile uint32_t *)0x40000C34 = 0x00000002; |
|
} |
|
|
|
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); |
|
} |
|
|
|
#define TEMP_NODE DT_INST(0, nordic_nrf_temp) |
|
|
|
#if DT_NODE_HAS_STATUS(TEMP_NODE, okay) |
|
static inline const struct device *temp_device(void) |
|
{ |
|
return device_get_binding(DT_LABEL(TEMP_NODE)); |
|
} |
|
#else |
|
#define temp_device() NULL |
|
#endif |
|
|
|
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 = temp_device(); |
|
|
|
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; |
|
}
|
|
|