text: /*
   * 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, (CONFIG_TEMP_NRF5_NAME), (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;
  }
  