Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
/*
 * Copyright (c) 2017 - 2018, Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#include <counter.h>
#include <nrfx_timer.h>

#define LOG_LEVEL CONFIG_COUNTER_LOG_LEVEL
#define LOG_MODULE_NAME counter_timer
#include <logging/log.h>
LOG_MODULE_REGISTER(LOG_MODULE_NAME, LOG_LEVEL);

#define TIMER_CLOCK 16000000

#define CC_TO_ID(cc_num) (cc_num - 2)

#define ID_TO_CC(idx) (nrf_timer_cc_channel_t)(idx + 2)

#define COUNTER_EVENT_TO_ID(evt) \
	(evt - NRF_TIMER_EVENT_COMPARE2)/sizeof(u32_t)

#define TOP_CH NRF_TIMER_CC_CHANNEL0
#define COUNTER_TOP_INT NRF_TIMER_EVENT_COMPARE0
#define COUNTER_OVERFLOW_SHORT NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK
#define COUNTER_READ_CC NRF_TIMER_CC_CHANNEL1

struct counter_nrfx_data {
	counter_top_callback_t top_cb;
	void *top_user_data;
};

struct counter_nrfx_ch_data {
	counter_alarm_callback_t callback;
	void *user_data;
};

struct counter_nrfx_config {
	struct counter_config_info info;
	struct counter_nrfx_ch_data *ch_data;
	nrfx_timer_t timer;

	LOG_INSTANCE_PTR_DECLARE(log);
};

static inline struct counter_nrfx_data *get_dev_data(struct device *dev)
{
	return dev->driver_data;
}

static inline const struct counter_nrfx_config *get_nrfx_config(
							struct device *dev)
{
	return CONTAINER_OF(dev->config->config_info,
				struct counter_nrfx_config, info);
}

static int counter_nrfx_start(struct device *dev)
{
	nrfx_timer_enable(&get_nrfx_config(dev)->timer);

	return 0;
}

static int counter_nrfx_stop(struct device *dev)
{
	nrfx_timer_disable(&get_nrfx_config(dev)->timer);

	return 0;
}

static u32_t counter_nrfx_get_top_value(struct device *dev)
{
	return nrfx_timer_capture_get(&get_nrfx_config(dev)->timer, TOP_CH);
}

static u32_t counter_nrfx_get_max_relative_alarm(struct device *dev)
{
	return nrfx_timer_capture_get(&get_nrfx_config(dev)->timer, TOP_CH);
}

static u32_t counter_nrfx_read(struct device *dev)
{
	return nrfx_timer_capture(&get_nrfx_config(dev)->timer,
				  COUNTER_READ_CC);
}

/** @brief Calculate compare value.
 *
 * If ticks are relative then compare value must take into consideration
 * counter wrapping.
 *
 * @return Compare value to be used in TIMER channel.
 */
static inline u32_t counter_nrfx_get_cc_value(struct device *dev,
				const struct counter_alarm_cfg *alarm_cfg)
{
	u32_t remainder;
	u32_t cc_val;
	u32_t ticks = alarm_cfg->ticks;

	if (alarm_cfg->absolute) {
		return ticks;
	}

	cc_val = counter_nrfx_read(dev);
	remainder = counter_nrfx_get_top_value(dev) - cc_val;

	if (remainder > ticks) {
		cc_val += ticks;
	} else {
		cc_val = ticks - remainder;
	}

	return cc_val;
}

static int counter_nrfx_set_alarm(struct device *dev, u8_t chan_id,
				  const struct counter_alarm_cfg *alarm_cfg)
{
	const struct counter_nrfx_config *nrfx_config = get_nrfx_config(dev);
	const nrfx_timer_t *timer = &nrfx_config->timer;
	u32_t cc_val;

	if (alarm_cfg->ticks > nrfx_timer_capture_get(timer, TOP_CH)) {
		return -EINVAL;
	}

	if (nrfx_config->ch_data[chan_id].callback) {
		return -EBUSY;
	}

	cc_val = counter_nrfx_get_cc_value(dev, alarm_cfg);

	nrfx_config->ch_data[chan_id].callback = alarm_cfg->callback;
	nrfx_config->ch_data[chan_id].user_data = alarm_cfg->user_data;

	nrfx_timer_compare(timer, ID_TO_CC(chan_id), cc_val, true);

	return 0;
}

static void _disable(struct device *dev, u8_t id)
{
	const struct counter_nrfx_config *config = get_nrfx_config(dev);

	nrfx_timer_compare_int_disable(&config->timer, ID_TO_CC(id));
	config->ch_data[id].callback = NULL;
}

static int counter_nrfx_cancel_alarm(struct device *dev, u8_t chan_id)
{
	_disable(dev, chan_id);

	return 0;
}


static int counter_nrfx_set_top_value(struct device *dev, u32_t ticks,
				      counter_top_callback_t callback,
				      void *user_data)
{
	const struct counter_nrfx_config *nrfx_config = get_nrfx_config(dev);
	const nrfx_timer_t *timer = &nrfx_config->timer;
	struct counter_nrfx_data *data = get_dev_data(dev);

	for (int i = 0; i < counter_get_num_of_channels(dev); i++) {
		/* Overflow can be changed only when all alarms are
		 * disables.
		 */
		if (nrfx_config->ch_data[i].callback) {
			return -EBUSY;
		}
	}

	nrfx_timer_compare_int_disable(timer, TOP_CH);
	nrfx_timer_clear(timer);

	data->top_cb = callback;
	data->top_user_data = user_data;
	nrfx_timer_extended_compare(timer, TOP_CH,
				    ticks, COUNTER_OVERFLOW_SHORT,
				    callback ? true : false);

	return 0;
}

static u32_t counter_nrfx_get_pending_int(struct device *dev)
{
	return 0;
}

static void alarm_event_handler(struct device *dev, u32_t id)
{
	const struct counter_nrfx_config *config = get_nrfx_config(dev);
	counter_alarm_callback_t clbk = config->ch_data[id].callback;
	u32_t cc_val;

	if (!clbk) {
		return;
	}

	cc_val = nrfx_timer_capture_get(&config->timer, ID_TO_CC(id));
	_disable(dev, id);
	clbk(dev, id, cc_val, config->ch_data[id].user_data);
}

static void event_handler(nrf_timer_event_t event_type, void *p_context)
{
	struct device *dev = p_context;
	struct counter_nrfx_data *dev_data = get_dev_data(dev);

	if (event_type == COUNTER_TOP_INT) {
		if (dev_data->top_cb) {
			dev_data->top_cb(dev, dev_data->top_user_data);
		}
	} else if (event_type > NRF_TIMER_EVENT_COMPARE1) {
		alarm_event_handler(dev, COUNTER_EVENT_TO_ID(event_type));

	}
}

static int init_timer(struct device *dev, const nrfx_timer_config_t *config)
{
	const struct counter_nrfx_config *nrfx_config = get_nrfx_config(dev);
	const nrfx_timer_t *timer = &nrfx_config->timer;

	nrfx_err_t result = nrfx_timer_init(timer, config, event_handler);

	if (result != NRFX_SUCCESS) {
		LOG_INST_ERR(nrfx_config->log, "Failed to initialize device.");
		return -EBUSY;
	}

	nrfx_timer_compare(timer, TOP_CH, UINT32_MAX, false);

	LOG_INST_DBG(nrfx_config->log, "Initialized");

	return 0;
}

static const struct counter_driver_api counter_nrfx_driver_api = {
	.start = counter_nrfx_start,
	.stop = counter_nrfx_stop,
	.read = counter_nrfx_read,
	.set_alarm = counter_nrfx_set_alarm,
	.cancel_alarm = counter_nrfx_cancel_alarm,
	.set_top_value = counter_nrfx_set_top_value,
	.get_pending_int = counter_nrfx_get_pending_int,
	.get_top_value = counter_nrfx_get_top_value,
	.get_max_relative_alarm = counter_nrfx_get_max_relative_alarm,
};

#define COUNTER_NRFX_TIMER_DEVICE(idx)					       \
	static int counter_##idx##_init(struct device *dev)		       \
	{								       \
		IRQ_CONNECT(DT_NORDIC_NRF_TIMER_TIMER_##idx##_IRQ,	       \
			    DT_NORDIC_NRF_TIMER_TIMER_##idx##_IRQ_PRIORITY,    \
			    nrfx_isr, nrfx_timer_##idx##_irq_handler, 0);      \
		const nrfx_timer_config_t config = {			       \
			.frequency = CONFIG_COUNTER_TIMER##idx##_PRESCALER,    \
			.mode      = NRF_TIMER_MODE_TIMER,		       \
			.bit_width = (TIMER##idx##_MAX_SIZE == 32) ?	       \
					NRF_TIMER_BIT_WIDTH_32 :	       \
					NRF_TIMER_BIT_WIDTH_16,		       \
			.p_context = dev				       \
		};							       \
		return init_timer(dev, &config);			       \
	}								       \
	static struct counter_nrfx_data counter_##idx##_data;		       \
	static struct counter_nrfx_ch_data				       \
		counter##idx##_ch_data[CC_TO_ID(TIMER##idx##_CC_NUM)];	       \
	LOG_INSTANCE_REGISTER(LOG_MODULE_NAME, idx, CONFIG_COUNTER_LOG_LEVEL); \
	static const struct counter_nrfx_config nrfx_counter_##idx##_config = {\
		.info = {						       \
			.max_top_value = (TIMER##idx##_MAX_SIZE == 32) ?       \
					0xffffffff : 0x0000ffff,	       \
			.freq = TIMER_CLOCK /				       \
				(1 << CONFIG_COUNTER_TIMER##idx##_PRESCALER),  \
			.count_up = true,				       \
			.channels = CC_TO_ID(TIMER##idx##_CC_NUM),	       \
		},							       \
		.ch_data = counter##idx##_ch_data,			       \
		.timer = NRFX_TIMER_INSTANCE(idx),			       \
		LOG_INSTANCE_PTR_INIT(log, LOG_MODULE_NAME, idx)	       \
	};								       \
	DEVICE_AND_API_INIT(timer_##idx,				       \
			    DT_NORDIC_NRF_TIMER_TIMER_##idx##_LABEL,	       \
			    counter_##idx##_init,			       \
			    &counter_##idx##_data,			       \
			    &nrfx_counter_##idx##_config.info,		       \
			    PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,  \
			    &counter_nrfx_driver_api)

#ifdef CONFIG_COUNTER_TIMER0
COUNTER_NRFX_TIMER_DEVICE(0);
#endif

#ifdef CONFIG_COUNTER_TIMER1
COUNTER_NRFX_TIMER_DEVICE(1);
#endif

#ifdef CONFIG_COUNTER_TIMER2
COUNTER_NRFX_TIMER_DEVICE(2);
#endif

#ifdef CONFIG_COUNTER_TIMER3
COUNTER_NRFX_TIMER_DEVICE(3);
#endif

#ifdef CONFIG_COUNTER_TIMER4
COUNTER_NRFX_TIMER_DEVICE(4);
#endif