From bcaf0bc655df337bc02c5dd78bfc3f4487103959 Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Tue, 29 Oct 2024 11:42:22 -0600 Subject: Implement RP2040 hw alarms --- libhw/rp2040_hwtimer.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 libhw/rp2040_hwtimer.c (limited to 'libhw/rp2040_hwtimer.c') diff --git a/libhw/rp2040_hwtimer.c b/libhw/rp2040_hwtimer.c new file mode 100644 index 0000000..b8255b8 --- /dev/null +++ b/libhw/rp2040_hwtimer.c @@ -0,0 +1,120 @@ +/* libhw/rp2040_hwtimer.c - Manage the RP2040's hardware timer + * + * Copyright (C) 2024 Luke T. Shumaker + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include /* pico-sdk:hardware_irq */ +#include /* pico-sdk:hardware_timer */ + +#include + +#define IMPLEMENTATION_FOR_LIBHW_RP2040_HWALARM_H YES +#include +#include + +uint64_t bootclock_get_ns(void) { + return timer_time_us_64(timer_hw)*(NS_PER_S/US_PER_S); +} + +static struct { + bool initialized; + struct rp2040_hwalarm_trigger *queue; +} alarms[_RP2040_HWALARM_NUM] = {0}; + +static void rp2040_hwalarm_intrhandler(void) { + uint irq_num = __get_current_exception() - VTABLE_FIRST_IRQ; + uint alarm_num = TIMER_ALARM_NUM_FROM_IRQ(irq_num); + assert(alarm_num < _RP2040_HWALARM_NUM); + + typeof(alarms[0]) *alarm = &alarms[alarm_num]; + + uint64_t now_us = timer_time_us_64(timer_hw); + while (alarm->queue && alarm->queue->fire_at_us <= now_us) { + struct rp2040_hwalarm_trigger *trigger = alarm->queue; + trigger->cb(trigger->cb_arg); + alarm->queue = trigger->next; + trigger->alarm = NULL; + trigger->next = NULL; + trigger->prev = NULL; + now_us = timer_time_us_64(timer_hw); + } + + hw_clear_bits(&timer_hw->intf, 1 << alarm_num); + if (alarm->queue) + timer_hw->alarm[alarm_num] = (uint32_t)alarm->queue->fire_at_us; +} + +bool rp2040_hwalarm_trigger_init(struct rp2040_hwalarm_trigger *self, + enum rp2040_hwalarm_instance alarm_num, + uint64_t fire_at_ns, + void (*cb)(void *), + void *cb_arg) { + assert(self); + assert(alarm_num < _RP2040_HWALARM_NUM); + assert(fire_at_ns); + assert(cb); + + uint64_t fire_at_us = (fire_at_ns + (NS_PER_S/US_PER_S) - 1)/(NS_PER_S/US_PER_S); + + uint64_t now_us = timer_time_us_64(timer_hw); + if (fire_at_us > now_us && (fire_at_us - now_us) > UINT32_MAX) + /* Too far in the future. */ + return true; + + typeof(alarms[0]) *alarm = &alarms[alarm_num]; + + self->alarm = alarm; + self->fire_at_us = fire_at_us; + self->cb = cb; + self->cb_arg = cb_arg; + + cr_disable_interrupts(); + struct rp2040_hwalarm_trigger **dst = &alarm->queue; + while (*dst && fire_at_us >= (*dst)->fire_at_us) + dst = &(*dst)->next; + self->next = *dst; + self->prev = *dst ? (*dst)->prev : NULL; + if (*dst) + (*dst)->prev = self; + *dst = self; + if (!alarm->initialized) { + hw_set_bits(&timer_hw->inte, 1 << alarm_num); + irq_set_exclusive_handler(TIMER_ALARM_IRQ_NUM(timer_hw, alarm_num), + rp2040_hwalarm_intrhandler); + irq_set_enabled(TIMER_ALARM_IRQ_NUM(timer_hw, alarm_num), true); + alarm->initialized = true; + } + if (alarm->queue == self) { + /* Force the interrupt handler to trigger as soon as + * we enable interrupts. This handles the case of + * when fire_at_us is before when we called + * cr_disable_interrupts(). We could check + * timer_time_us_64() again after calling + * cr_disable_interrupts() and do this conditionally, + * but I don't think that would be any more efficient + * than just letting the interrupt fire. */ + hw_set_bits(&timer_hw->intf, 1 << alarm_num); + } + cr_enable_interrupts(); + + return false; +} + +void rp2040_hwalarm_trigger_cancel(struct rp2040_hwalarm_trigger *self) { + assert(self); + cr_disable_interrupts(); + if (self->alarm) { + typeof(alarms[0]) *alarm = self->alarm; + if (!self->prev) + alarm->queue = self->next; + else + self->prev->next = self->next; + if (self->next) + self->next->prev = self->prev; + self->alarm = NULL; + self->prev = NULL; + self->next = NULL; + } + cr_enable_interrupts(); +} -- cgit v1.2.3-2-g168b