text: /*
   * Copyright (c) 2021 Nordic Semiconductor ASA
   *
   * SPDX-License-Identifier: Apache-2.0
   */
  #include <drivers/entropy.h>
  #include <drivers/counter.h>
  #include <drivers/gpio.h>
  #include "busy_sim.h"
  #include <sys/ring_buffer.h>
  #include <random/rand32.h>
  
  #define BUFFER_SIZE 32
  
  struct busy_sim_data {
  	uint32_t idle_avg;
  	uint32_t active_avg;
  	uint16_t idle_delta;
  	uint16_t active_delta;
  	uint32_t us_tick;
  	struct counter_alarm_cfg alarm_cfg;
  	busy_sim_cb_t cb;
  };
  
  static struct k_work work;
  static struct ring_buf rnd_rbuf;
  static uint8_t rnd_buf[BUFFER_SIZE];
  
  struct busy_sim_config {
  	const struct device *entropy;
  	const struct device *counter;
  	struct gpio_dt_spec pin_spec;
  };
  
  #define DT_BUSY_SIM DT_COMPAT_GET_ANY_STATUS_OKAY(vnd_busy_sim)
  
  BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(vnd_busy_sim) == 1,
  	     "add exactly one vnd,busy-sim node to the devicetree");
  
  static const struct busy_sim_config sim_config = {
  	.entropy = COND_CODE_1(CONFIG_XOSHIRO_RANDOM_GENERATOR,
  			       (NULL),
  			       (DEVICE_DT_GET(DT_CHOSEN(zephyr_entropy)))),
  	.counter = DEVICE_DT_GET(DT_PHANDLE(DT_BUSY_SIM, counter)),
  	.pin_spec = GPIO_DT_SPEC_GET_OR(DT_BUSY_SIM, active_gpios, {0}),
  };
  
  static struct busy_sim_data sim_data;
  static const struct device *busy_sim_dev = DEVICE_DT_GET_ONE(vnd_busy_sim);
  
  static void rng_pool_work_handler(struct k_work *work)
  {
  	uint8_t *buf;
  	uint32_t len;
  	const struct busy_sim_config *config = busy_sim_dev->config;
  
  	len = ring_buf_put_claim(&rnd_rbuf, &buf, BUFFER_SIZE - 1);
  	if (len) {
  		int err = entropy_get_entropy(config->entropy, buf, len);
  
  		if (err == 0) {
  			ring_buf_put_finish(&rnd_rbuf, len);
  			return;
  		}
  	}
  
  	k_work_submit(work);
  }
  
  
  static uint32_t get_timeout(bool idle)
  {
  	struct busy_sim_data *data = busy_sim_dev->data;
  	uint32_t avg = idle ? data->idle_avg : data->active_avg;
  	uint32_t delta = idle ? data->idle_delta : data->active_delta;
  	uint16_t rand_val;
  	uint32_t len;
  
  	if (IS_ENABLED(CONFIG_XOSHIRO_RANDOM_GENERATOR)) {
  		sys_rand_get(&rand_val, sizeof(rand_val));
  	} else {
  		len = ring_buf_get(&rnd_rbuf,
  				   (uint8_t *)&rand_val,
  				   sizeof(rand_val));
  		if (len < sizeof(rand_val)) {
  			k_work_submit(&work);
  			rand_val = 0;
  		}
  	}
  
  	avg *= data->us_tick;
  	delta *= data->us_tick;
  
  	return avg - delta + 2 * (rand_val % delta);
  }
  
  static void counter_alarm_callback(const struct device *dev,
  				   uint8_t chan_id, uint32_t ticks,
  				   void *user_data)
  {
  	int err;
  	const struct busy_sim_config *config = busy_sim_dev->config;
  	struct busy_sim_data *data = busy_sim_dev->data;
  
  	data->alarm_cfg.ticks = get_timeout(true);
  
  	if (config->pin_spec.port) {
  		err = gpio_pin_set_dt(&config->pin_spec, 1);
  		__ASSERT_NO_MSG(err >= 0);
  	}
  
  	/* Busy loop */
  	if (data->cb) {
  		data->cb();
  	}
  
  	k_busy_wait(get_timeout(false) / data->us_tick);
  
  	if (config->pin_spec.port) {
  		err = gpio_pin_set_dt(&config->pin_spec, 0);
  		__ASSERT_NO_MSG(err >= 0);
  	}
  
  	err = counter_set_channel_alarm(config->counter, 0, &data->alarm_cfg);
  	__ASSERT_NO_MSG(err == 0);
  
  }
  
  void busy_sim_start(uint32_t active_avg, uint32_t active_delta,
  		    uint32_t idle_avg, uint32_t idle_delta, busy_sim_cb_t cb)
  {
  	int err;
  	const struct busy_sim_config *config = busy_sim_dev->config;
  	struct busy_sim_data *data = busy_sim_dev->data;
  
  	data->cb = cb;
  	data->active_avg = active_avg;
  	data->active_delta = active_delta;
  	data->idle_avg = idle_avg;
  	data->idle_delta = idle_delta;
  
  	if (!IS_ENABLED(CONFIG_XOSHIRO_RANDOM_GENERATOR)) {
  		err = k_work_submit(&work);
  		__ASSERT_NO_MSG(err >= 0);
  	}
  
  	data->alarm_cfg.ticks = counter_us_to_ticks(config->counter, 100);
  	err = counter_set_channel_alarm(config->counter, 0, &data->alarm_cfg);
  	__ASSERT_NO_MSG(err == 0);
  
  	err = counter_start(config->counter);
  	__ASSERT_NO_MSG(err == 0);
  }
  
  void busy_sim_stop(void)
  {
  	int err;
  	const struct busy_sim_config *config = busy_sim_dev->config;
  
  	if (!IS_ENABLED(CONFIG_XOSHIRO_RANDOM_GENERATOR)) {
  		k_work_cancel(&work);
  	}
  
  	err = counter_stop(config->counter);
  	__ASSERT_NO_MSG(err == 0);
  }
  
  static int busy_sim_init(const struct device *dev)
  {
  	uint32_t freq;
  	const struct busy_sim_config *config = dev->config;
  	struct busy_sim_data *data = dev->data;
  
  	if ((config->pin_spec.port && !device_is_ready(config->pin_spec.port)) ||
  	    !device_is_ready(config->counter) ||
  	    (!IS_ENABLED(CONFIG_XOSHIRO_RANDOM_GENERATOR) &&
  	    !device_is_ready(config->entropy))) {
  		__ASSERT(0, "Devices needed by busy simulator not ready.");
  		return -EIO;
  	}
  
  	if (config->pin_spec.port) {
  		gpio_pin_configure_dt(&config->pin_spec, GPIO_OUTPUT | GPIO_OUTPUT_INIT_LOW);
  	}
  
  	freq = counter_get_frequency(config->counter);
  	if (freq < 1000000) {
  		__ASSERT(0, "Counter device has too low frequency for busy simulator.");
  		return -EINVAL;
  	}
  
  	if (!IS_ENABLED(CONFIG_XOSHIRO_RANDOM_GENERATOR)) {
  		k_work_init(&work, rng_pool_work_handler);
  		ring_buf_init(&rnd_rbuf, BUFFER_SIZE, rnd_buf);
  	}
  
  	data->us_tick = freq / 1000000;
  	data->alarm_cfg.callback = counter_alarm_callback;
  	data->alarm_cfg.flags = COUNTER_ALARM_CFG_EXPIRE_WHEN_LATE;
  
  	return 0;
  }
  
  DEVICE_DT_DEFINE(DT_BUSY_SIM, busy_sim_init, NULL,
  	      &sim_data, &sim_config,
  	      APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY,
  	      NULL);
  