mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-09-06 03:21:57 +00:00
Update the binding for nordic,nrf-temp to require the label property and use the generated define (DT_INST_0_NORDIC_NRF_TEMP_LABEL) instead of Kconfig symbol (CONFIG_TEMP_NRF5_NAME). Signed-off-by: Kumar Gala <kumar.gala@linaro.org>
332 lines
8.0 KiB
C
332 lines
8.0 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 <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 *hfclk_dev; /* Handler to hfclk device. */
|
|
static struct device *temp_sensor; /* Handler to temperature sensor device. */
|
|
|
|
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(evt) &&
|
|
nrf_clock_int_enable_check(intmask);
|
|
|
|
if (ret) {
|
|
nrf_clock_event_clear(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(hfclk_dev, 0, &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_TASK_CTSTOP);
|
|
nrf_clock_event_clear(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_EVENT_DONE);
|
|
nrf_clock_event_clear(NRF_CLOCK_EVENT_CTTO);
|
|
|
|
nrf_clock_int_enable(NRF_CLOCK_INT_DONE_MASK |
|
|
NRF_CLOCK_INT_CTTO_MASK |
|
|
NRF_CLOCK_INT_CTSTOPPED_MASK);
|
|
nrf_clock_cal_timer_timeout_set(
|
|
CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_PERIOD);
|
|
|
|
if (CONFIG_CLOCK_CONTROL_NRF_CALIBRATION_MAX_SKIP != 0) {
|
|
temp_sensor = device_get_binding(TEMP_SENSOR_NAME);
|
|
}
|
|
|
|
hfclk_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_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(hfclk_dev, 0);
|
|
nrf_clock_task_trigger(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 s16_t get_temperature(void)
|
|
{
|
|
struct sensor_value sensor_val;
|
|
|
|
sensor_sample_fetch(temp_sensor);
|
|
sensor_channel_get(temp_sensor, SENSOR_CHAN_DIE_TEMP, &sensor_val);
|
|
|
|
return sensor_value_to_temp_unit(&sensor_val);
|
|
}
|
|
|
|
/* 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;
|
|
s16_t diff;
|
|
bool started = false;
|
|
int key;
|
|
|
|
temperature = get_temperature();
|
|
diff = abs(temperature - prev_temperature);
|
|
|
|
key = irq_lock();
|
|
|
|
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++;
|
|
}
|
|
}
|
|
|
|
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(hfclk_dev, 0);
|
|
}
|
|
|
|
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(hfclk_dev, 0);
|
|
nrf_clock_task_trigger(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(hfclk_dev, 0, &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(hfclk_dev, 0, &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 &&
|
|
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_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;
|
|
}
|