drivers: counter native: Add top value conf and multi channel support

The counter_native_posix driver currently does not support top value
configuration, i.e. `ctr_set_top_value` returns `-ENOTSUP`. This commit
adds support for top value configuration, and with the counter API now
fully implemented, adds `counter` to `supported` peripherals for
native_posix target.
It also resolves an existing bug in which the
counter ISR did not reset upon reaching `TOP_VALUE`.
And adds support for multiple channels

Signed-off-by: Jason Wright <jason@jpw.nyc>
Signed-off-by: Alberto Escolar Piedras <alberto.escolar.piedras@nordicsemi.no>
This commit is contained in:
Jason Wright 2023-10-12 22:47:18 -04:00 committed by Alberto Escolar
parent 7cf4eff731
commit 7e02a0379f
8 changed files with 157 additions and 27 deletions

View file

@ -17,6 +17,7 @@ static bool counter_running;
static uint64_t counter_value;
static uint64_t counter_target;
static uint64_t counter_period;
static uint64_t counter_wrap;
/**
* Initialize the counter with prescaler of HW
@ -28,6 +29,7 @@ void hw_counter_init(void)
counter_value = 0;
counter_running = false;
counter_period = NEVER;
counter_wrap = NEVER;
}
void hw_counter_triggered(void)
@ -38,7 +40,7 @@ void hw_counter_triggered(void)
}
hw_counter_timer = hwm_get_time() + counter_period;
counter_value = counter_value + 1;
counter_value = (counter_value + 1) % counter_wrap;
if (counter_value == counter_target) {
hw_irq_ctrl_set_irq(COUNTER_EVENT_IRQ);
@ -54,6 +56,16 @@ void hw_counter_set_period(uint64_t period)
counter_period = period;
}
/*
* Set the count value at which the counter will wrap
* The counter will count up to (counter_wrap-1), i.e.:
* 0, 1, 2,.., (counter_wrap - 1), 0
*/
void hw_counter_set_wrap_value(uint64_t wrap_value)
{
counter_wrap = wrap_value;
}
/**
* Starts the counter. It must be previously configured with
* hw_counter_set_period() and hw_counter_set_target().
@ -82,6 +94,11 @@ void hw_counter_stop(void)
hwm_find_next_timer();
}
bool hw_counter_is_started(void)
{
return counter_running;
}
/**
* Returns the current counter value.
*/
@ -90,6 +107,14 @@ uint64_t hw_counter_get_value(void)
return counter_value;
}
/**
* Resets the counter value.
*/
void hw_counter_reset(void)
{
counter_value = 0;
}
/**
* Configures the counter to generate an interrupt
* when its count value reaches target.

View file

@ -18,9 +18,12 @@ void hw_counter_triggered(void);
void hw_counter_set_period(uint64_t period);
void hw_counter_set_target(uint64_t counter_target);
void hw_counter_set_wrap_value(uint64_t wrap_value);
void hw_counter_start(void);
void hw_counter_stop(void);
bool hw_counter_is_started(void);
uint64_t hw_counter_get_value(void);
void hw_counter_reset(void);
#ifdef __cplusplus
}

View file

@ -10,6 +10,7 @@ toolchain:
- llvm
supported:
- can
- counter
- eeprom
- netif:eth
- usb_device

View file

@ -10,6 +10,7 @@ toolchain:
- llvm
supported:
- can
- counter
- eeprom
- netif:eth
- usb_device

View file

@ -10,6 +10,7 @@ toolchain:
- llvm
supported:
- can
- counter
- eeprom
- netif:eth
- usb_device

View file

@ -10,6 +10,7 @@ toolchain:
- llvm
supported:
- can
- counter
- eeprom
- netif:eth
- usb_device

View file

@ -10,3 +10,8 @@ config COUNTER_NATIVE_POSIX_FREQUENCY
int "native_posix counter frequency in Hz"
default 1000
depends on COUNTER_NATIVE_POSIX
config COUNTER_NATIVE_POSIX_NBR_CHANNELS
int "native counter, number of channels"
default 4
depends on COUNTER_NATIVE_POSIX

View file

@ -6,6 +6,7 @@
#define DT_DRV_COMPAT zephyr_native_posix_counter
#include <string.h>
#include <zephyr/device.h>
#include <zephyr/drivers/counter.h>
#include <zephyr/irq.h>
@ -14,38 +15,80 @@
#include <limits.h>
#define DRIVER_CONFIG_INFO_FLAGS (COUNTER_CONFIG_INFO_COUNT_UP)
#define DRIVER_CONFIG_INFO_CHANNELS 1
#define DRIVER_CONFIG_INFO_CHANNELS CONFIG_COUNTER_NATIVE_POSIX_NBR_CHANNELS
#define COUNTER_NATIVE_POSIX_IRQ_FLAGS (0)
#define COUNTER_NATIVE_POSIX_IRQ_PRIORITY (2)
#define COUNTER_PERIOD (USEC_PER_SEC / CONFIG_COUNTER_NATIVE_POSIX_FREQUENCY)
#define TOP_VALUE (UINT_MAX)
static struct counter_alarm_cfg pending_alarm;
static bool is_alarm_pending;
static struct counter_alarm_cfg pending_alarm[DRIVER_CONFIG_INFO_CHANNELS];
static bool is_alarm_pending[DRIVER_CONFIG_INFO_CHANNELS];
static struct counter_top_cfg top;
static bool is_top_set;
static const struct device *device;
static void schedule_next_isr(void)
{
int64_t current_value = hw_counter_get_value();
uint32_t next_time = top.ticks; /* top.ticks is TOP_VALUE if is_top_set == false */
if (current_value == top.ticks) {
current_value = -1;
}
for (int i = 0; i < DRIVER_CONFIG_INFO_CHANNELS; i++) {
if (is_alarm_pending[i]) {
if (pending_alarm[i].ticks > current_value) {
/* If the alarm is not after a wrap */
next_time = MIN(pending_alarm[i].ticks, next_time);
}
}
}
/* We will at least get an interrupt at top.ticks even if is_top_set == false,
* which is fine. We may use that to set the next alarm if needed
*/
hw_counter_set_target(next_time);
}
static void counter_isr(const void *arg)
{
ARG_UNUSED(arg);
uint32_t current_value = hw_counter_get_value();
if (is_alarm_pending) {
is_alarm_pending = false;
pending_alarm.callback(device, 0, current_value,
pending_alarm.user_data);
for (int i = 0; i < DRIVER_CONFIG_INFO_CHANNELS; i++) {
if (is_alarm_pending[i] && (current_value == pending_alarm[i].ticks)) {
is_alarm_pending[i] = false;
if (pending_alarm[i].callback) {
pending_alarm[i].callback(device, i, current_value,
pending_alarm[i].user_data);
}
}
}
if (is_top_set && (current_value == top.ticks)) {
if (top.callback) {
top.callback(device, top.user_data);
}
}
schedule_next_isr();
}
static int ctr_init(const struct device *dev)
{
device = dev;
is_alarm_pending = false;
memset(is_alarm_pending, 0, sizeof(is_alarm_pending));
is_top_set = false;
top.ticks = TOP_VALUE;
IRQ_CONNECT(COUNTER_EVENT_IRQ, COUNTER_NATIVE_POSIX_IRQ_PRIORITY,
counter_isr, NULL, COUNTER_NATIVE_POSIX_IRQ_FLAGS);
irq_enable(COUNTER_EVENT_IRQ);
hw_counter_set_period(COUNTER_PERIOD);
hw_counter_set_target(TOP_VALUE);
hw_counter_set_wrap_value((uint64_t)top.ticks + 1);
hw_counter_reset();
return 0;
}
@ -54,6 +97,7 @@ static int ctr_start(const struct device *dev)
{
ARG_UNUSED(dev);
schedule_next_isr();
hw_counter_start();
return 0;
}
@ -80,19 +124,56 @@ static uint32_t ctr_get_pending_int(const struct device *dev)
return 0;
}
static bool is_any_alarm_pending(void)
{
for (int i = 0; i < DRIVER_CONFIG_INFO_CHANNELS; i++) {
if (is_alarm_pending[i]) {
return true;
}
}
return false;
}
static int ctr_set_top_value(const struct device *dev,
const struct counter_top_cfg *cfg)
{
ARG_UNUSED(dev);
ARG_UNUSED(cfg);
posix_print_warning("%s not supported\n", __func__);
return -ENOTSUP;
if (is_any_alarm_pending()) {
posix_print_warning("Can't set top value while alarm is active\n");
return -EBUSY;
}
uint32_t current_value = hw_counter_get_value();
if (cfg->flags & COUNTER_TOP_CFG_DONT_RESET) {
if (current_value >= cfg->ticks) {
if (cfg->flags & COUNTER_TOP_CFG_RESET_WHEN_LATE) {
hw_counter_reset();
}
return -ETIME;
}
} else {
hw_counter_reset();
}
top = *cfg;
hw_counter_set_wrap_value((uint64_t)top.ticks + 1);
if ((cfg->ticks == TOP_VALUE) && !cfg->callback) {
is_top_set = false;
} else {
is_top_set = true;
}
schedule_next_isr();
return 0;
}
static uint32_t ctr_get_top_value(const struct device *dev)
{
return TOP_VALUE;
return top.ticks;
}
static int ctr_set_alarm(const struct device *dev, uint8_t chan_id,
@ -100,21 +181,31 @@ static int ctr_set_alarm(const struct device *dev, uint8_t chan_id,
{
ARG_UNUSED(dev);
if (chan_id >= DRIVER_CONFIG_INFO_CHANNELS) {
posix_print_warning("channel %u is not supported\n", chan_id);
return -ENOTSUP;
}
if (is_alarm_pending[chan_id])
return -EBUSY;
pending_alarm = *alarm_cfg;
is_alarm_pending = true;
uint32_t ticks = alarm_cfg->ticks;
if (ticks > top.ticks) {
posix_print_warning("Alarm ticks %u exceed top ticks %u\n", ticks,
top.ticks);
return -EINVAL;
}
if (!(alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE)) {
pending_alarm.ticks =
hw_counter_get_value() + pending_alarm.ticks;
uint32_t current_value = hw_counter_get_value();
ticks += current_value;
if (ticks > top.ticks) { /* Handle wrap arounds */
ticks -= (top.ticks + 1); /* The count period is top.ticks + 1 */
}
}
hw_counter_set_target(pending_alarm.ticks);
irq_enable(COUNTER_EVENT_IRQ);
pending_alarm[chan_id] = *alarm_cfg;
pending_alarm[chan_id].ticks = ticks;
is_alarm_pending[chan_id] = true;
schedule_next_isr();
return 0;
}
@ -123,12 +214,14 @@ static int ctr_cancel_alarm(const struct device *dev, uint8_t chan_id)
{
ARG_UNUSED(dev);
if (chan_id >= DRIVER_CONFIG_INFO_CHANNELS) {
posix_print_warning("channel %u is not supported\n", chan_id);
if (!hw_counter_is_started()) {
posix_print_warning("Counter not started\n");
return -ENOTSUP;
}
is_alarm_pending = false;
is_alarm_pending[chan_id] = false;
schedule_next_isr();
return 0;
}