mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-09-02 08:52:28 +00:00
Low frequency and high frequency clocks had separate devices while they are actually handled by single peripheral with single interrupt. The split was done probably because opaque subsys argument in the API was used for other purposes and there was no way to pass the information which clock should be controlled. Implementation changes some time ago and subsys parameter was no longer used. It now can be used to indicate which clock should be controlled. Change become necessary when nrf5340 is taken into account where there are more clocks and current approach would lead to create multiple devices - mess. Signed-off-by: Krzysztof Chruscinski <krzysztof.chruscinski@nordicsemi.no>
352 lines
8.5 KiB
C
352 lines
8.5 KiB
C
/*
|
|
* Copyright (c) 2019 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include <sensor.h>
|
|
#include <drivers/clock_control.h>
|
|
#include "nrf_clock_calibration.h"
|
|
#include <drivers/clock_control/nrf_clock_control.h>
|
|
#include <hal/nrf_clock.h>
|
|
#include <logging/log.h>
|
|
#include <stdlib.h>
|
|
|
|
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.
|
|
*/
|
|
#ifndef CLOCK_EVENTS_CTSTOPPED_EVENTS_CTSTOPPED_Msk
|
|
#define NRF_CLOCK_EVENT_CTSTOPPED 0
|
|
#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 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. */
|
|
|
|
/* Callback called on hfclk started. */
|
|
static void cal_hf_on_callback(struct device *dev, void *user_data);
|
|
static struct clock_control_async_data cal_hf_on_data = {
|
|
.cb = cal_hf_on_callback
|
|
};
|
|
|
|
static struct device *clk_dev;
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
void z_nrf_clock_calibration_init(struct device *dev)
|
|
{
|
|
/* 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;
|
|
}
|
|
|
|
/* Start calibration assuming that HFCLK XTAL is on. */
|
|
static void start_calibration(void)
|
|
{
|
|
cal_state = CAL_ACTIVE;
|
|
|
|
/* Workaround for Errata 192 */
|
|
if (IS_ENABLED(CONFIG_SOC_SERIES_NRF52X)) {
|
|
*(volatile uint32_t *)0x40000C34 = 0x00000002;
|
|
}
|
|
|
|
nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_CAL);
|
|
calib_skip_cnt = CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP;
|
|
}
|
|
|
|
/* Restart calibration timer, release HFCLK XTAL. */
|
|
static void to_idle(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);
|
|
}
|
|
|
|
/* Convert sensor value to 0.25'C units. */
|
|
static inline s16_t sensor_value_to_temp_unit(struct sensor_value *val)
|
|
{
|
|
return (s16_t)(4 * val->val1 + val->val2 / 250000);
|
|
}
|
|
|
|
/* Function reads from temperature sensor and converts to 0.25'C units. */
|
|
static int get_temperature(s16_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)
|
|
{
|
|
s16_t temperature = 0;
|
|
s16_t diff;
|
|
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;
|
|
}
|
|
|
|
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++;
|
|
}
|
|
}
|
|
|
|
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, void *user_data)
|
|
{
|
|
int key = irq_lock();
|
|
|
|
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);
|
|
}
|
|
|
|
irq_unlock(key);
|
|
}
|
|
|
|
static void on_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.");
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
irq_unlock(key);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|