| /* |
| * 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; |
| } |