drivers: clock_control: nrf: Reworked calibration to use k_timer

Reworked calibration to not use HW platform which is not available on
some platforms (nrf53) and does not bring any value compared to using
system timer. Switched to use k_timer. Additionally, improved
calibration algorithm to request also LF clock before starting
calibration. This simplifies the algorithm because it does not need
to support disabling low frequency clock which in calibration.

Tests has been updated and simplified. Former tests relied on HW timer
event.

Signed-off-by: Krzysztof Chruscinski <krzysztof.chruscinski@nordicsemi.no>
This commit is contained in:
Krzysztof Chruscinski 2020-02-07 13:27:13 +01:00 committed by Ioannis Glaropoulos
commit a36a478954
8 changed files with 345 additions and 516 deletions

View file

@ -53,9 +53,8 @@ config CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION
if CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION
config CLOCK_CONTROL_NRF_CALIBRATION_PERIOD
int "Calibration opportunity period (in 250ms units)"
default 16
range 1 127
int "Calibration opportunity period in milliseconds"
default 4000
help
Periodically, calibration action is performed. Action includes
temperature measurement followed by clock calibration. Calibration may

View file

@ -13,36 +13,32 @@
LOG_MODULE_DECLARE(clock_control, CONFIG_CLOCK_CONTROL_LOG_LEVEL);
/* For platforms that do not have CTSTOPPED event CT timer can be started
* immediately after stop. Redefined events to avoid ifdefs in the code,
* CTSTOPPED interrupt handling will be removed during compilation.
/**
* 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.
*/
#ifndef CLOCK_EVENTS_CTSTOPPED_EVENTS_CTSTOPPED_Msk
#define NRF_CLOCK_EVENT_CTSTOPPED 0
#ifndef DT_INST_0_NORDIC_NRF_TEMP_LABEL
#define DT_INST_0_NORDIC_NRF_TEMP_LABEL ""
#endif
#ifndef CLOCK_INTENSET_CTSTOPPED_Msk
#define NRF_CLOCK_INT_CTSTOPPED_MASK 0
#endif
#define TEMP_SENSOR_NAME \
COND_CODE_1(CONFIG_TEMP_NRF5, (DT_INST_0_NORDIC_NRF_TEMP_LABEL), (NULL))
/* Calibration state enum */
enum nrf_cal_state {
CAL_OFF,
CAL_IDLE, /* Calibration timer active, waiting for expiration. */
CAL_HFCLK_REQ, /* HFCLK XTAL requested. */
CAL_TEMP_REQ, /* Temperature measurement requested. */
CAL_ACTIVE, /* Ongoing calibration. */
CAL_ACTIVE_OFF /* Ongoing calibration, off requested. */
};
static enum nrf_cal_state cal_state; /* Calibration state. */
static atomic_t cal_process_in_progress;
static s16_t prev_temperature; /* Previous temperature measurement. */
static u8_t calib_skip_cnt; /* Counting down skipped calibrations. */
static int total_cnt; /* Total number of calibrations. */
static int total_skips_cnt; /* Total number of skipped calibrations. */
static volatile int total_cnt; /* Total number of calibrations. */
static volatile int total_skips_cnt; /* Total number of skipped calibrations. */
/* Callback called on hfclk started. */
static void cal_hf_on_callback(struct device *dev,
@ -51,6 +47,11 @@ static void cal_hf_on_callback(struct device *dev,
static struct clock_control_async_data cal_hf_on_data = {
.cb = cal_hf_on_callback
};
static void cal_lf_on_callback(struct device *dev,
clock_control_subsys_t subsys, void *user_data);
static struct clock_control_async_data cal_lf_on_data = {
.cb = cal_lf_on_callback
};
static struct device *clk_dev;
static struct device *temp_sensor;
@ -58,102 +59,40 @@ static struct device *temp_sensor;
static void measure_temperature(struct k_work *work);
static K_WORK_DEFINE(temp_measure_work, measure_temperature);
static bool clock_event_check_and_clean(u32_t evt, u32_t intmask)
static void timeout_handler(struct k_timer *timer);
static K_TIMER_DEFINE(backoff_timer, timeout_handler, NULL);
static void hf_request(void)
{
bool ret = nrf_clock_event_check(NRF_CLOCK, evt) &&
nrf_clock_int_enable_check(NRF_CLOCK, intmask);
if (ret) {
nrf_clock_event_clear(NRF_CLOCK, evt);
}
return ret;
}
bool z_nrf_clock_calibration_start(struct device *dev)
{
bool ret;
int key = irq_lock();
if (cal_state != CAL_ACTIVE_OFF) {
ret = true;
} else {
ret = false;
}
cal_state = CAL_IDLE;
irq_unlock(key);
calib_skip_cnt = 0;
return ret;
}
void z_nrf_clock_calibration_lfclk_started(struct device *dev)
{
/* Trigger unconditional calibration when lfclk is started. */
cal_state = CAL_HFCLK_REQ;
clock_control_async_on(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_HF,
&cal_hf_on_data);
}
bool z_nrf_clock_calibration_stop(struct device *dev)
static void hf_release(void)
{
int key;
bool ret = true;
key = irq_lock();
nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_CTSTOP);
nrf_clock_event_clear(NRF_CLOCK, NRF_CLOCK_EVENT_CTTO);
/* If calibration is active then pend until completed.
* Currently (and most likely in the future), LFCLK is never stopped so
* it is not an issue.
*/
if (cal_state == CAL_ACTIVE) {
cal_state = CAL_ACTIVE_OFF;
ret = false;
} else {
cal_state = CAL_OFF;
}
irq_unlock(key);
LOG_DBG("Stop requested %s.", (cal_state == CAL_ACTIVE_OFF) ?
"during ongoing calibration" : "");
return ret;
clock_control_off(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_HF);
}
void z_nrf_clock_calibration_init(struct device *dev)
static void lf_request(void)
{
/* Anomaly 36: After watchdog timeout reset, CPU lockup reset, soft
* reset, or pin reset EVENTS_DONE and EVENTS_CTTO are not reset.
*/
nrf_clock_event_clear(NRF_CLOCK, NRF_CLOCK_EVENT_DONE);
nrf_clock_event_clear(NRF_CLOCK, NRF_CLOCK_EVENT_CTTO);
nrf_clock_int_enable(NRF_CLOCK, NRF_CLOCK_INT_DONE_MASK |
NRF_CLOCK_INT_CTTO_MASK |
NRF_CLOCK_INT_CTSTOPPED_MASK);
nrf_clock_cal_timer_timeout_set(NRF_CLOCK,
CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD);
if (CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP != 0) {
temp_sensor = device_get_binding(TEMP_SENSOR_NAME);
}
clk_dev = dev;
total_cnt = 0;
total_skips_cnt = 0;
clock_control_async_on(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_LF,
&cal_lf_on_data);
}
/* Start calibration assuming that HFCLK XTAL is on. */
static void start_calibration(void)
static void lf_release(void)
{
cal_state = CAL_ACTIVE;
clock_control_off(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_LF);
}
static void cal_lf_on_callback(struct device *dev,
clock_control_subsys_t subsys, void *user_data)
{
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;
@ -163,12 +102,59 @@ static void start_calibration(void)
calib_skip_cnt = CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP;
}
/* Restart calibration timer, release HFCLK XTAL. */
static void to_idle(void)
/* Start cycle by starting backoff timer and releasing HFCLK XTAL. */
static void start_cycle(void)
{
cal_state = CAL_IDLE;
clock_control_off(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_HF);
nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_CTSTART);
k_timer_start(&backoff_timer,
K_MSEC(CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD), 0);
hf_release();
lf_release();
cal_process_in_progress = 0;
}
static void start_cal_process(void)
{
if (atomic_cas(&cal_process_in_progress, 0, 1) == false) {
return;
}
/* 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_on_callback(struct device *dev,
clock_control_subsys_t subsys, void *user_data)
{
if ((temp_sensor == NULL) || !IS_ENABLED(CONFIG_MULTITHREADING)) {
start_hw_cal();
} else {
k_work_submit(&temp_measure_work);
}
}
static void on_hw_cal_done(void)
{
/* Workaround for Errata 192 */
if (IS_ENABLED(CONFIG_SOC_SERIES_NRF52X)) {
*(volatile uint32_t *)0x40000C34 = 0x00000000;
}
total_cnt++;
LOG_DBG("Calibration done.");
start_cycle();
}
/* Convert sensor value to 0.25'C units. */
@ -200,139 +186,84 @@ static int get_temperature(s16_t *tvp)
static void measure_temperature(struct k_work *work)
{
s16_t temperature = 0;
s16_t diff;
s16_t diff = 0;
bool started = false;
int key;
int rc;
rc = get_temperature(&temperature);
key = irq_lock();
if (rc != 0) {
/* Temperature read failed: retry later */
to_idle();
goto out;
/* Temperature read failed, force calibration. */
calib_skip_cnt = 0;
} else {
diff = abs(temperature - prev_temperature);
}
diff = abs(temperature - prev_temperature);
if (cal_state != CAL_OFF) {
if ((calib_skip_cnt == 0) ||
(diff >= CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_TEMP_DIFF)) {
prev_temperature = temperature;
start_calibration();
started = true;
} else {
to_idle();
calib_skip_cnt--;
total_skips_cnt++;
}
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();
}
out:
irq_unlock(key);
LOG_DBG("Calibration %s. Temperature diff: %d (in 0.25'C units).",
started ? "started" : "skipped", diff);
}
/* Called when HFCLK XTAL is on. Schedules temperature measurement or triggers
* calibration.
*/
static void cal_hf_on_callback(struct device *dev,
clock_control_subsys_t subsys,
void *user_data)
void z_nrf_clock_calibration_init(struct device *dev)
{
int key = irq_lock();
/* Anomaly 36: After watchdog timeout reset, CPU lockup reset, soft
* reset, or pin reset EVENTS_DONE and EVENTS_CTTO are not reset.
*/
nrf_clock_event_clear(NRF_CLOCK, NRF_CLOCK_EVENT_DONE);
nrf_clock_int_enable(NRF_CLOCK, NRF_CLOCK_INT_DONE_MASK);
if (cal_state == CAL_HFCLK_REQ) {
if ((CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP == 0) ||
(IS_ENABLED(CONFIG_MULTITHREADING) == false)) {
start_calibration();
} else {
cal_state = CAL_TEMP_REQ;
k_work_submit(&temp_measure_work);
}
} else {
clock_control_off(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_HF);
if (CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP != 0) {
temp_sensor =
device_get_binding(DT_INST_0_NORDIC_NRF_TEMP_LABEL);
}
irq_unlock(key);
clk_dev = dev;
total_cnt = 0;
total_skips_cnt = 0;
}
static void on_cal_done(void)
static void start_unconditional_cal_process(void)
{
/* Workaround for Errata 192 */
if (IS_ENABLED(CONFIG_SOC_SERIES_NRF52X)) {
*(volatile uint32_t *)0x40000C34 = 0x00000000;
}
total_cnt++;
LOG_DBG("Calibration done.");
int key = irq_lock();
if (cal_state == CAL_ACTIVE_OFF) {
clock_control_off(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_HF);
nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_LFCLKSTOP);
cal_state = CAL_OFF;
} else {
to_idle();
}
irq_unlock(key);
calib_skip_cnt = 0;
start_cal_process();
}
void z_nrf_clock_calibration_force_start(void)
{
int key = irq_lock();
calib_skip_cnt = 0;
if (cal_state == CAL_IDLE) {
cal_state = CAL_HFCLK_REQ;
clock_control_async_on(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_HF,
&cal_hf_on_data);
/* if it's already in progress that is good enough. */
if (cal_process_in_progress) {
return;
}
irq_unlock(key);
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_isr(void)
{
if (clock_event_check_and_clean(NRF_CLOCK_EVENT_CTTO,
NRF_CLOCK_INT_CTTO_MASK)) {
LOG_DBG("Calibration timeout.");
/* Start XTAL HFCLK. It is needed for temperature measurement
* and calibration.
*/
if (cal_state == CAL_IDLE) {
cal_state = CAL_HFCLK_REQ;
clock_control_async_on(clk_dev,
CLOCK_CONTROL_NRF_SUBSYS_HF,
&cal_hf_on_data);
}
}
if (clock_event_check_and_clean(NRF_CLOCK_EVENT_DONE,
NRF_CLOCK_INT_DONE_MASK)) {
on_cal_done();
}
if ((NRF_CLOCK_INT_CTSTOPPED_MASK != 0) &&
clock_event_check_and_clean(NRF_CLOCK_EVENT_CTSTOPPED,
NRF_CLOCK_INT_CTSTOPPED_MASK)) {
LOG_INF("CT stopped.");
if (cal_state == CAL_IDLE) {
/* If LF clock was restarted then CT might not be
* started because it was not yet stopped.
*/
LOG_INF("restarting");
nrf_clock_task_trigger(NRF_CLOCK,
NRF_CLOCK_TASK_CTSTART);
}
if (nrf_clock_event_check(NRF_CLOCK, NRF_CLOCK_EVENT_DONE)) {
nrf_clock_event_clear(NRF_CLOCK, NRF_CLOCK_EVENT_DONE);
on_hw_cal_done();
}
}

View file

@ -26,39 +26,15 @@ void z_nrf_clock_calibration_init(struct device *hfclk_dev);
*/
void z_nrf_clock_calibration_isr(void);
/**
* @brief Start calibration.
*
* Function called when LFCLK RC clock is being started.
*
* @param dev LFCLK device.
*
* @retval true if clock can be started.
* @retval false if clock was not stopped due to ongoing calibration and don't
* need to be started again because it is still on.
*/
bool z_nrf_clock_calibration_start(struct device *dev);
/**
* @brief Notify calibration module about LF clock start
*
* @param dev LFCLK device.
*/
void z_nrf_clock_calibration_lfclk_started(struct device *dev);
void z_nrf_clock_calibration_lfclk_started(void);
/**
* @brief Stop calibration.
*
* Function called when LFCLK RC clock is being stopped.
*
* @param dev LFCLK device.
*
* @retval true if clock can be stopped.
* @retval false if due to ongoing calibration clock cannot be stopped. In that
* case calibration module will stop clock when calibration is
* completed.
* @brief Notify calibration module about LF clock stop
*/
bool z_nrf_clock_calibration_stop(struct device *dev);
void z_nrf_clock_calibration_lfclk_stopped(void);
#ifdef __cplusplus

View file

@ -30,11 +30,6 @@ LOG_MODULE_REGISTER(clock_control, CONFIG_CLOCK_CONTROL_LOG_LEVEL);
#define INF(dev, subsys, ...) CLOCK_LOG(INF, dev, subsys, __VA_ARGS__)
#define DBG(dev, subsys, ...) CLOCK_LOG(DBG, dev, subsys, __VA_ARGS__)
/* returns true if clock stopping or starting can be performed. If false then
* start/stop will be deferred and performed later on by handler owner.
*/
typedef bool (*nrf_clock_handler_t)(struct device *dev);
/* Clock subsys structure */
struct nrf_clock_control_sub_data {
sys_slist_t list; /* List of users requesting callback */
@ -44,8 +39,6 @@ struct nrf_clock_control_sub_data {
/* Clock subsys static configuration */
struct nrf_clock_control_sub_config {
nrf_clock_handler_t start_handler; /* Called before start */
nrf_clock_handler_t stop_handler; /* Called before stop */
nrf_clock_event_t started_evt; /* Clock started event */
nrf_clock_task_t start_tsk; /* Clock start task */
nrf_clock_task_t stop_tsk; /* Clock stop task */
@ -161,25 +154,16 @@ static int clock_stop(struct device *dev, clock_control_subsys_t subsys)
}
data->ref--;
if (data->ref == 0) {
bool do_stop;
DBG(dev, subsys, "Stopping");
sys_slist_init(&data->list);
do_stop = (config->stop_handler) ?
config->stop_handler(dev) : true;
if (do_stop) {
nrf_clock_task_trigger(NRF_CLOCK, config->stop_tsk);
/* It may happen that clock is being stopped when it
* has just been started and start is not yet handled
* (due to irq_lock). In that case after stopping the
* clock, started event is cleared to prevent false
* interrupt being triggered.
*/
nrf_clock_event_clear(NRF_CLOCK, config->started_evt);
if (IS_ENABLED(CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION)
&& (subsys == CLOCK_CONTROL_NRF_SUBSYS_LF)) {
z_nrf_clock_calibration_lfclk_stopped();
}
nrf_clock_task_trigger(NRF_CLOCK, config->stop_tsk);
data->started = false;
}
@ -286,31 +270,14 @@ static int clock_async_start(struct device *dev,
}
if (ref == 1) {
bool do_start;
DBG(dev, subsys, "Triggering start task");
do_start = (config->start_handler) ?
config->start_handler(dev) : true;
if (do_start) {
DBG(dev, subsys, "Triggering start task");
if (IS_ENABLED(CONFIG_NRF52_ANOMALY_132_WORKAROUND) &&
(subsys == CLOCK_CONTROL_NRF_SUBSYS_LF)) {
anomaly_132_workaround();
}
nrf_clock_task_trigger(NRF_CLOCK,
config->start_tsk);
} else {
/* If external start_handler indicated that clcok is
* still running (it may happen in case of LF RC clock
* which was requested to be stopped during ongoing
* calibration (clock will not be stopped in that case)
* and requested to be started before calibration is
* completed. In that case clock is still running and
* we can notify enlisted requests.
*/
clkstarted_handle(dev, type);
if (IS_ENABLED(CONFIG_NRF52_ANOMALY_132_WORKAROUND) &&
(subsys == CLOCK_CONTROL_NRF_SUBSYS_LF)) {
anomaly_132_workaround();
}
nrf_clock_task_trigger(NRF_CLOCK, config->start_tsk);
}
return 0;
@ -376,13 +343,6 @@ static const struct nrf_clock_control_config config = {
.started_evt = NRF_CLOCK_EVENT_LFCLKSTARTED,
.stop_tsk = NRF_CLOCK_TASK_LFCLKSTOP,
IF_ENABLED(CONFIG_LOG, (.name = "lfclk",))
IF_ENABLED(
CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION,
(
.start_handler = z_nrf_clock_calibration_start,
.stop_handler = z_nrf_clock_calibration_stop,
)
)
}
}
};
@ -466,7 +426,7 @@ void nrf_power_clock_isr(void *arg)
NRF_CLOCK_INT_LF_STARTED_MASK)) {
if (IS_ENABLED(
CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION)) {
z_nrf_clock_calibration_lfclk_started(dev);
z_nrf_clock_calibration_lfclk_started();
}
clkstarted_handle(dev, CLOCK_CONTROL_NRF_TYPE_LFCLK);
}

View file

@ -0,0 +1,9 @@
# Copyright (c) 2020 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
config CLOCK_CONTROL_NRF_USES_TEMP_SENSOR
bool "Internal"
help
Used internally to enable mocking of temperature sensor
source "Kconfig.zephyr"

View file

@ -1,6 +1,11 @@
CONFIG_ZTEST=y
CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC=y
CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP=0
CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD=1
CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP=1
CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_TEMP_DIFF=2
CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD=150
CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_DEBUG=y
#disable temperature sensor, mock is used
CONFIG_SENSOR=y
CONFIG_TEMP_NRF5=n
CONFIG_CLOCK_CONTROL_NRF_USES_TEMP_SENSOR=n

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <device.h>
#include <drivers/sensor.h>
/* Mock of internal temperature sensore. */
#ifdef CONFIG_TEMP_NRF5
#error "Cannot be enabled because it is being mocked"
#endif
static struct sensor_value value;
void mock_temp_nrf5_value_set(struct sensor_value *val)
{
value = *val;
}
static int mock_temp_nrf5_init(struct device *dev)
{
return 0;
}
static int mock_temp_nrf5_sample_fetch(struct device *dev,
enum sensor_channel chan)
{
k_sleep(K_MSEC(1));
return 0;
}
static int mock_temp_nrf5_channel_get(struct device *dev,
enum sensor_channel chan,
struct sensor_value *val)
{
*val = value;
return 0;
}
static const struct sensor_driver_api mock_temp_nrf5_driver_api = {
.sample_fetch = mock_temp_nrf5_sample_fetch,
.channel_get = mock_temp_nrf5_channel_get,
};
DEVICE_AND_API_INIT(mock_temp_nrf5,
DT_INST_0_NORDIC_NRF_TEMP_LABEL,
mock_temp_nrf5_init,
NULL,
NULL,
POST_KERNEL,
CONFIG_SENSOR_INIT_PRIORITY,
&mock_temp_nrf5_driver_api);

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Nordic Semiconductor ASA
* Copyright (c) 2019-2020, Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -7,6 +7,7 @@
#include <drivers/clock_control.h>
#include <drivers/clock_control/nrf_clock_control.h>
#include <hal/nrf_clock.h>
#include <drivers/sensor.h>
#include <logging/log.h>
LOG_MODULE_REGISTER(test);
@ -14,9 +15,18 @@ LOG_MODULE_REGISTER(test);
#error "LFCLK must use RC source"
#endif
#if CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD != 1
#error "Expected 250ms calibration period"
#endif
#define CALIBRATION_PROCESS_TIME_MS 35
extern void mock_temp_nrf5_value_set(struct sensor_value *val);
static void turn_on_clock(struct device *dev, clock_control_subsys_t subsys)
{
clock_control_on(dev, subsys);
while (clock_control_get_status(dev, subsys) !=
CLOCK_CONTROL_STATUS_ON) {
}
}
static void turn_off_clock(struct device *dev, clock_control_subsys_t subsys)
{
@ -27,255 +37,141 @@ static void turn_off_clock(struct device *dev, clock_control_subsys_t subsys)
} while (err == 0);
}
static void lfclk_started_cb(struct device *dev,
clock_control_subsys_t subsys,
void *user_data)
{
*(bool *)user_data = true;
}
#define TEST_CALIBRATION(exp_cal, exp_skip, sleep_ms) \
test_calibration(exp_cal, exp_skip, sleep_ms, __LINE__)
/* Test checks if calibration clock is running and generates interrupt as
* expected and starts calibration. Validates that HF clock is turned on
* for calibration and turned off once calibration is done.
/* Function tests if during given time expected number of calibrations and
* skips occurs.
*/
static void test_clock_calibration(void)
static void test_calibration(u32_t exp_cal, u32_t exp_skip,
u32_t sleep_ms, u32_t line)
{
struct device *clk_dev =
device_get_binding(DT_INST_0_NORDIC_NRF_CLOCK_LABEL);
volatile bool started = false;
struct clock_control_async_data lfclk_data = {
.cb = lfclk_started_cb,
.user_data = (void *)&started
};
int key;
u32_t cnt = 0;
u32_t max_cnt = 1000;
int cal_cnt;
int skip_cnt;
turn_off_clock(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_LF);
turn_off_clock(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_HF);
cal_cnt = z_nrf_clock_calibration_count();
skip_cnt = z_nrf_clock_calibration_skips_count();
/* In case calibration needs to be completed. */
k_busy_wait(100000);
k_sleep(K_MSEC(sleep_ms));
clock_control_async_on(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_LF,
&lfclk_data);
cal_cnt = z_nrf_clock_calibration_count() - cal_cnt;
skip_cnt = z_nrf_clock_calibration_skips_count() - skip_cnt;
while (started == false) {
}
k_busy_wait(35000);
key = irq_lock();
while (nrf_clock_event_check(NRF_CLOCK, NRF_CLOCK_EVENT_CTTO) == 0) {
k_busy_wait(1000);
cnt++;
if (cnt == max_cnt) {
irq_unlock(key);
clock_control_off(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_LF);
zassert_true(false, "");
}
}
zassert_within(cnt, 250, 10, "Expected 250ms period");
irq_unlock(key);
while (clock_control_get_status(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_HF)
!= CLOCK_CONTROL_STATUS_ON) {
}
key = irq_lock();
cnt = 0;
while (nrf_clock_event_check(NRF_CLOCK, NRF_CLOCK_EVENT_DONE) == 0) {
k_busy_wait(1000);
cnt++;
if (cnt == max_cnt) {
irq_unlock(key);
clock_control_off(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_LF);
zassert_true(false, "");
}
}
irq_unlock(key);
zassert_equal(clock_control_get_status(
clk_dev, CLOCK_CONTROL_NRF_SUBSYS_HF),
CLOCK_CONTROL_STATUS_OFF,
"Expected hfclk off after calibration.");
key = irq_lock();
cnt = 0;
while (nrf_clock_event_check(NRF_CLOCK, NRF_CLOCK_EVENT_CTTO) == 0) {
k_busy_wait(1000);
cnt++;
if (cnt == max_cnt) {
irq_unlock(key);
clock_control_off(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_LF);
zassert_true(false, "");
}
}
zassert_within(cnt, 250, 10, "Expected 250ms period (got %d)", cnt);
irq_unlock(key);
clock_control_off(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_LF);
zassert_equal(cal_cnt, exp_cal,
"%d: Unexpected number of calibrations (%d, exp:%d)",
line, cal_cnt, exp_cal);
zassert_equal(skip_cnt, exp_skip,
"%d: Unexpected number of skips (%d, exp:%d)",
line, skip_cnt, exp_skip);
}
/* Test checks that when calibration is active then LF clock is not stopped.
* Stopping is deferred until calibration is done. Test validates that after
* completing calibration LF clock is shut down.
/* Function pends until calibration counter is performed. When function leaves,
* it is just after calibration.
*/
static void test_stopping_when_calibration(void)
static void sync_just_after_calibration(void)
{
u32_t cal_cnt = z_nrf_clock_calibration_count();
/* wait until calibration is performed. */
while (z_nrf_clock_calibration_count() == cal_cnt) {
k_sleep(K_MSEC(1));
}
}
/* Test checks if calibration and calibration skips are performed according
* to timing configuration.
*/
static void test_basic_clock_calibration(void)
{
int wait_ms = CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD *
(CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP + 1) +
CALIBRATION_PROCESS_TIME_MS;
struct sensor_value value = { .val1 = 0, .val2 = 0 };
mock_temp_nrf5_value_set(&value);
sync_just_after_calibration();
TEST_CALIBRATION(1, CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP,
wait_ms);
}
/* Test checks if calibration happens just after clock is enabled. */
static void test_calibration_after_enabling_lfclk(void)
{
struct device *clk_dev =
device_get_binding(DT_INST_0_NORDIC_NRF_CLOCK_LABEL);
volatile bool started = false;
struct clock_control_async_data lfclk_data = {
.cb = lfclk_started_cb,
.user_data = (void *)&started
};
int key;
u32_t cnt = 0;
u32_t max_cnt = 1000;
struct sensor_value value = { .val1 = 0, .val2 = 0 };
mock_temp_nrf5_value_set(&value);
turn_off_clock(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_LF);
turn_off_clock(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_HF);
/* In case calibration needs to be completed. */
k_busy_wait(100000);
k_busy_wait(10000);
clock_control_async_on(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_LF,
&lfclk_data);
turn_on_clock(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_LF);
while (started == false) {
}
/* when lfclk is started first calibration is performed. Wait until it
* is done.
*/
k_busy_wait(35000);
key = irq_lock();
while (nrf_clock_event_check(NRF_CLOCK, NRF_CLOCK_EVENT_CTTO) == 0) {
k_busy_wait(1000);
cnt++;
if (cnt == max_cnt) {
irq_unlock(key);
clock_control_off(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_LF);
zassert_true(false, "");
}
}
zassert_within(cnt, 250, 10, "Expected 250ms period");
irq_unlock(key);
while (clock_control_get_status(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_HF)
!= CLOCK_CONTROL_STATUS_ON) {
}
/* calibration started */
key = irq_lock();
clock_control_off(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_LF);
zassert_true(nrf_clock_lf_is_running(NRF_CLOCK),
"Expected LF still on");
while (nrf_clock_event_check(NRF_CLOCK, NRF_CLOCK_EVENT_DONE) == 0) {
k_busy_wait(1000);
cnt++;
if (cnt == max_cnt) {
irq_unlock(key);
clock_control_off(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_LF);
zassert_true(false, "");
}
}
irq_unlock(key);
/* wait some time after which clock should be off. */
k_busy_wait(300);
zassert_false(nrf_clock_lf_is_running(NRF_CLOCK), "Expected LF off");
clock_control_off(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_LF);
TEST_CALIBRATION(1, 0,
CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD);
}
static u32_t pend_on_next_calibration(void)
/* Test checks if temperature change triggers calibration. */
static void test_temp_change_triggers_calibration(void)
{
u32_t stamp = k_uptime_get_32();
int cnt = z_nrf_clock_calibration_count();
struct sensor_value value = { .val1 = 0, .val2 = 0 };
while (cnt == z_nrf_clock_calibration_count()) {
if ((k_uptime_get_32() - stamp) > 300) {
zassert_true(false, "Expected calibration");
return UINT32_MAX;
}
}
mock_temp_nrf5_value_set(&value);
sync_just_after_calibration();
return k_uptime_get_32() - stamp;
/* change temperature by 0.25'C which should not trigger calibration */
value.val2 += ((CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_TEMP_DIFF - 1) *
250000);
mock_temp_nrf5_value_set(&value);
/* expected one skip */
TEST_CALIBRATION(0, CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP,
CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP *
CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD +
CALIBRATION_PROCESS_TIME_MS);
TEST_CALIBRATION(1, 0,
CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD + 40);
value.val2 += (CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_TEMP_DIFF * 250000);
mock_temp_nrf5_value_set(&value);
/* expect calibration due to temp change. */
TEST_CALIBRATION(1, 0,
CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD + 40);
}
static void test_clock_calibration_force(void)
/* Test checks if z_nrf_clock_calibration_force_start() results in immediate
* calibration.
*/
static void test_force_calibration(void)
{
struct device *clk_dev =
device_get_binding(DT_INST_0_NORDIC_NRF_CLOCK_LABEL);
volatile bool started = false;
struct clock_control_async_data lfclk_data = {
.cb = lfclk_started_cb,
.user_data = (void *)&started
};
u32_t cnt = 0;
u32_t period;
sync_just_after_calibration();
turn_off_clock(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_LF);
turn_off_clock(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_HF);
/* In case calibration needs to be completed. */
k_busy_wait(100000);
clock_control_async_on(clk_dev, CLOCK_CONTROL_NRF_SUBSYS_LF,
&lfclk_data);
while (started == false) {
}
cnt = z_nrf_clock_calibration_count();
pend_on_next_calibration();
zassert_equal(cnt + 1, z_nrf_clock_calibration_count(),
"Unexpected number of calibrations %d (expected: %d)",
z_nrf_clock_calibration_count(), cnt + 1);
/* Next calibration should happen in 250ms, after forcing it should be
* done sooner.
*/
z_nrf_clock_calibration_force_start();
period = pend_on_next_calibration();
zassert_equal(cnt + 2, z_nrf_clock_calibration_count(),
"Unexpected number of calibrations");
zassert_true(period < 100, "Unexpected calibration period.");
k_busy_wait(80000);
/*expect immediate calibration */
TEST_CALIBRATION(1, 0,
CALIBRATION_PROCESS_TIME_MS + 5);
/* Next calibration should happen as scheduled. */
period = pend_on_next_calibration();
zassert_equal(cnt + 3, z_nrf_clock_calibration_count(),
"Unexpected number of calibrations");
zassert_true(period < 200,
"Unexpected calibration period %d ms.", period);
/* and back to scheduled operation. */
TEST_CALIBRATION(1, CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP,
CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD *
(CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP + 1) +
CALIBRATION_PROCESS_TIME_MS);
}
void test_main(void)
{
ztest_test_suite(test_nrf_clock_calibration,
ztest_unit_test(test_clock_calibration),
ztest_unit_test(test_stopping_when_calibration),
ztest_unit_test(test_clock_calibration_force)
ztest_unit_test(test_basic_clock_calibration),
ztest_unit_test(test_calibration_after_enabling_lfclk),
ztest_unit_test(test_temp_change_triggers_calibration),
ztest_unit_test(test_force_calibration)
);
ztest_run_test_suite(test_nrf_clock_calibration);
}