diff --git a/boards/posix/native_posix/hw_counter.c b/boards/posix/native_posix/hw_counter.c index 77218a7ada41a8..dd6f4bc7ad6cdf 100644 --- a/boards/posix/native_posix/hw_counter.c +++ b/boards/posix/native_posix/hw_counter.c @@ -82,6 +82,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 +95,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. diff --git a/boards/posix/native_posix/hw_counter.h b/boards/posix/native_posix/hw_counter.h index 5cd6dd2e57784b..c6a2ee4928ce03 100644 --- a/boards/posix/native_posix/hw_counter.h +++ b/boards/posix/native_posix/hw_counter.h @@ -20,7 +20,9 @@ void hw_counter_set_period(uint64_t period); void hw_counter_set_target(uint64_t counter_target); 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 } diff --git a/boards/posix/native_posix/native_posix.yaml b/boards/posix/native_posix/native_posix.yaml index 7d10c080474411..27276573bad66b 100644 --- a/boards/posix/native_posix/native_posix.yaml +++ b/boards/posix/native_posix/native_posix.yaml @@ -18,6 +18,7 @@ supported: - spi - gpio - rtc + - counter testing: default: true vendor: zephyr diff --git a/boards/posix/native_posix/native_posix_64.yaml b/boards/posix/native_posix/native_posix_64.yaml index 62d3b168497703..addc3629e7f5f9 100644 --- a/boards/posix/native_posix/native_posix_64.yaml +++ b/boards/posix/native_posix/native_posix_64.yaml @@ -16,4 +16,5 @@ supported: - adc - gpio - rtc + - counter vendor: zephyr diff --git a/drivers/counter/counter_native_posix.c b/drivers/counter/counter_native_posix.c index aa221a11fdc472..a32c0b7547c749 100644 --- a/drivers/counter/counter_native_posix.c +++ b/drivers/counter/counter_native_posix.c @@ -23,17 +23,55 @@ static struct counter_alarm_cfg pending_alarm; static bool is_alarm_pending; +static struct counter_top_cfg top; +static bool is_top_set; static const struct device *device; +static uint32_t last_top; +static uint32_t next_top; static void counter_isr(const void *arg) { ARG_UNUSED(arg); uint32_t current_value = hw_counter_get_value(); - if (is_alarm_pending) { + /* Process `top` first to ensure new target is used in any subsequent + * calls to `ctr_set_alarm`. + */ + if (is_top_set && (current_value == (last_top + top.ticks))) { + if (top.callback) { + top.callback(device, top.user_data); + } + last_top = current_value; + next_top = current_value + top.ticks; + + if (next_top < last_top) { + hw_counter_set_target(TOP_VALUE); + } else { + hw_counter_set_target(next_top); + } + } + + if (is_alarm_pending && (current_value == pending_alarm.ticks)) { is_alarm_pending = false; - pending_alarm.callback(device, 0, current_value, - pending_alarm.user_data); + if (pending_alarm.callback) { + pending_alarm.callback(device, 0, current_value, + pending_alarm.user_data); + } + } + + /* Rollover when `TOP_VALUE` is reached, then set the next target */ + if (current_value == TOP_VALUE) { + hw_counter_reset(); + + if (is_alarm_pending && is_top_set) { + hw_counter_set_target(MIN(pending_alarm.ticks, next_top)); + } else if (is_alarm_pending) { + hw_counter_set_target(pending_alarm.ticks); + } else if (is_top_set) { + hw_counter_set_target(next_top); + } else { + hw_counter_set_target(TOP_VALUE); + } } } @@ -41,6 +79,7 @@ static int ctr_init(const struct device *dev) { device = dev; is_alarm_pending = false; + is_top_set = false; IRQ_CONNECT(COUNTER_EVENT_IRQ, COUNTER_NATIVE_POSIX_IRQ_PRIORITY, counter_isr, NULL, COUNTER_NATIVE_POSIX_IRQ_FLAGS); @@ -84,15 +123,42 @@ 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_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; + last_top = current_value; + + if ((cfg->ticks != TOP_VALUE) && cfg->callback) { + is_top_set = true; + hw_counter_set_target(current_value + cfg->ticks); + irq_enable(COUNTER_EVENT_IRQ); + } else { + is_top_set = false; + } + + 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, @@ -105,15 +171,33 @@ static int ctr_set_alarm(const struct device *dev, uint8_t chan_id, return -ENOTSUP; } + if (is_alarm_pending) + return -EBUSY; + + uint32_t ticks = alarm_cfg->ticks; + uint32_t current_value = hw_counter_get_value(); + + if (!(alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE)) { + ticks += current_value; + } + + if (is_top_set && ((ticks - current_value) > (top.ticks - current_value))) { + posix_print_warning("Alarm ticks %u exceed top ticks %u\n", ticks, + top.ticks); + return -EINVAL; + } + pending_alarm = *alarm_cfg; + pending_alarm.ticks = ticks; is_alarm_pending = true; - if (!(alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE)) { - pending_alarm.ticks = - hw_counter_get_value() + pending_alarm.ticks; + /* set target to sooner of `ticks` and `TOP_VALUE`, accounting for rollover */ + if ((TOP_VALUE - current_value) < (ticks - current_value)) { + hw_counter_set_target(TOP_VALUE); + } else { + hw_counter_set_target(pending_alarm.ticks); } - hw_counter_set_target(pending_alarm.ticks); irq_enable(COUNTER_EVENT_IRQ); return 0; @@ -128,6 +212,11 @@ static int ctr_cancel_alarm(const struct device *dev, uint8_t chan_id) return -ENOTSUP; } + if (!hw_counter_is_started()) { + posix_print_warning("Counter not started\n"); + return -ENOTSUP; + } + is_alarm_pending = false; return 0;