Linux Audio

Check our new training course

Loading...
/*
 * 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 <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,
				clock_control_subsys_t subsys,
				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,
				clock_control_subsys_t subsys,
				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;
}