summaryrefslogtreecommitdiff
path: root/libhw/rp2040_hwtimer.c
diff options
context:
space:
mode:
Diffstat (limited to 'libhw/rp2040_hwtimer.c')
-rw-r--r--libhw/rp2040_hwtimer.c120
1 files changed, 120 insertions, 0 deletions
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 <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <hardware/irq.h> /* pico-sdk:hardware_irq */
+#include <hardware/timer.h> /* pico-sdk:hardware_timer */
+
+#include <libcr/coroutine.h>
+
+#define IMPLEMENTATION_FOR_LIBHW_RP2040_HWALARM_H YES
+#include <libhw/rp2040_hwalarm.h>
+#include <libhw/generic/bootclock.h>
+
+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();
+}