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.c195
1 files changed, 120 insertions, 75 deletions
diff --git a/libhw/rp2040_hwtimer.c b/libhw/rp2040_hwtimer.c
index b8255b8..6daa69d 100644
--- a/libhw/rp2040_hwtimer.c
+++ b/libhw/rp2040_hwtimer.c
@@ -1,120 +1,165 @@
-/* libhw/rp2040_hwtimer.c - Manage the RP2040's hardware timer
+/* libhw/rp2040_hwtimer.c - <libhw/generic/alarmclock.h> implementation for the RP2040's hardware timer
*
* Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
+#include <assert.h>
+
#include <hardware/irq.h> /* pico-sdk:hardware_irq */
#include <hardware/timer.h> /* pico-sdk:hardware_timer */
#include <libcr/coroutine.h>
+#include <libmisc/vcall.h>
+
+#define IMPLEMENTATION_FOR_LIBHW_GENERIC_ALARMCLOCK_H YES
+#include <libhw/generic/alarmclock.h>
+#include <libhw/rp2040_hwtimer.h>
+
+/* Init ***********************************************************************/
+
+struct rp2040_hwtimer {
+ implements_alarmclock;
+ enum rp2040_hwalarm_instance alarm_num;
+ bool initialized;
+ struct alarmclock_trigger *queue;
+};
+
+static uint64_t rp2040_hwtimer_get_time_ns(implements_alarmclock *self);
+static bool rp2040_hwtimer_add_trigger(implements_alarmclock *self,
+ struct alarmclock_trigger *trigger,
+ uint64_t fire_at_ns,
+ void (*cb)(void *),
+ void *cb_arg);
+static void rp2040_hwtimer_del_trigger(implements_alarmclock *self,
+ struct alarmclock_trigger *trigger);
+
+static struct alarmclock_vtable rp2040_hwtimer_vtable = {
+ .get_time_ns = rp2040_hwtimer_get_time_ns,
+ .add_trigger = rp2040_hwtimer_add_trigger,
+ .del_trigger = rp2040_hwtimer_del_trigger,
+};
+
+static struct rp2040_hwtimer hwtimers[] = {
+ { .vtable = &rp2040_hwtimer_vtable, .alarm_num = 0 },
+ { .vtable = &rp2040_hwtimer_vtable, .alarm_num = 1 },
+ { .vtable = &rp2040_hwtimer_vtable, .alarm_num = 2 },
+ { .vtable = &rp2040_hwtimer_vtable, .alarm_num = 3 },
+};
+static_assert(sizeof(hwtimers)/sizeof(hwtimers[0]) == _RP2040_HWALARM_NUM);
+
+implements_alarmclock *rp2040_hwtimer(enum rp2040_hwalarm_instance alarm_num) {
+ assert(alarm_num < _RP2040_HWALARM_NUM);
+ return &hwtimers[alarm_num];
+}
-#define IMPLEMENTATION_FOR_LIBHW_RP2040_HWALARM_H YES
-#include <libhw/rp2040_hwalarm.h>
-#include <libhw/generic/bootclock.h>
+/* Main implementation ********************************************************/
-uint64_t bootclock_get_ns(void) {
- return timer_time_us_64(timer_hw)*(NS_PER_S/US_PER_S);
+static uint64_t rp2040_hwtimer_get_time_ns(implements_alarmclock *) {
+ 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;
-}
+#define NS_TO_US_ROUNDUP(x) ( ( (x) + (NS_PER_S/US_PER_S)-1) / (NS_PER_S/US_PER_S) )
-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);
+static void rp2040_hwtimer_intrhandler(void) {
+ uint irq_num = __get_current_exception() - VTABLE_FIRST_IRQ;
+ enum rp2040_hwalarm_instance alarm_num = TIMER_ALARM_NUM_FROM_IRQ(irq_num);
assert(alarm_num < _RP2040_HWALARM_NUM);
+
+ struct rp2040_hwtimer *alarmclock = &hwtimers[alarm_num];
+
+ while (alarmclock->queue &&
+ NS_TO_US_ROUNDUP(alarmclock->queue->fire_at_ns) <= timer_time_us_64(timer_hw)) {
+ struct alarmclock_trigger *trigger = alarmclock->queue;
+ trigger->cb(trigger->cb_arg);
+ alarmclock->queue = trigger->next;
+ trigger->alarmclock = NULL;
+ trigger->next = NULL;
+ trigger->prev = NULL;
+ }
+
+ hw_clear_bits(&timer_hw->intf, 1 << alarm_num);
+ if (alarmclock->queue)
+ timer_hw->alarm[alarm_num] = (uint32_t)NS_TO_US_ROUNDUP(alarmclock->queue->fire_at_ns);
+}
+
+static bool rp2040_hwtimer_add_trigger(implements_alarmclock *_alarmclock,
+ struct alarmclock_trigger *trigger,
+ uint64_t fire_at_ns,
+ void (*cb)(void *),
+ void *cb_arg) {
+ struct rp2040_hwtimer *alarmclock =
+ VCALL_SELF(struct rp2040_hwtimer, implements_alarmclock, _alarmclock);
+ assert(alarmclock);
+ assert(trigger);
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)
+ if (NS_TO_US_ROUNDUP(fire_at_ns) > now_us &&
+ (NS_TO_US_ROUNDUP(fire_at_ns) - 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;
+ trigger->alarmclock = alarmclock;
+ trigger->fire_at_ns = fire_at_ns;
+ trigger->cb = cb;
+ trigger->cb_arg = cb_arg;
cr_disable_interrupts();
- struct rp2040_hwalarm_trigger **dst = &alarm->queue;
- while (*dst && fire_at_us >= (*dst)->fire_at_us)
+ struct alarmclock_trigger **dst = &alarmclock->queue;
+ while (*dst && fire_at_ns >= (*dst)->fire_at_ns)
dst = &(*dst)->next;
- self->next = *dst;
- self->prev = *dst ? (*dst)->prev : NULL;
+ trigger->next = *dst;
+ trigger->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;
+ (*dst)->prev = trigger;
+ *dst = trigger;
+ if (!alarmclock->initialized) {
+ hw_set_bits(&timer_hw->inte, 1 << alarmclock->alarm_num);
+ irq_set_exclusive_handler(TIMER_ALARM_IRQ_NUM(timer_hw, alarmclock->alarm_num),
+ rp2040_hwtimer_intrhandler);
+ irq_set_enabled(TIMER_ALARM_IRQ_NUM(timer_hw, alarmclock->alarm_num), true);
+ alarmclock->initialized = true;
}
- if (alarm->queue == self) {
+ if (alarmclock->queue == trigger) {
/* 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
+ * when fire_at_ns 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);
+ hw_set_bits(&timer_hw->intf, 1 << alarmclock->alarm_num);
}
cr_enable_interrupts();
return false;
}
-void rp2040_hwalarm_trigger_cancel(struct rp2040_hwalarm_trigger *self) {
- assert(self);
+static void rp2040_hwtimer_del_trigger(implements_alarmclock *_alarmclock,
+ struct alarmclock_trigger *trigger) {
+ struct rp2040_hwtimer *alarmclock =
+ VCALL_SELF(struct rp2040_hwtimer, implements_alarmclock, _alarmclock);
+ assert(alarmclock);
+ assert(trigger);
+
cr_disable_interrupts();
- if (self->alarm) {
- typeof(alarms[0]) *alarm = self->alarm;
- if (!self->prev)
- alarm->queue = self->next;
+ if (trigger->alarmclock == alarmclock) {
+ if (!trigger->prev)
+ alarmclock->queue = trigger->next;
else
- self->prev->next = self->next;
- if (self->next)
- self->next->prev = self->prev;
- self->alarm = NULL;
- self->prev = NULL;
- self->next = NULL;
+ trigger->prev->next = trigger->next;
+ if (trigger->next)
+ trigger->next->prev = trigger->prev;
+ trigger->alarmclock = NULL;
+ trigger->prev = NULL;
+ trigger->next = NULL;
}
cr_enable_interrupts();
}
+
+/* Globals ********************************************************************/
+
+const implements_alarmclock *bootclock = &hwtimers[0];