summaryrefslogtreecommitdiff
path: root/libhw
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2024-10-29 11:42:22 -0600
committerLuke T. Shumaker <lukeshu@lukeshu.com>2024-10-29 11:42:22 -0600
commitbcaf0bc655df337bc02c5dd78bfc3f4487103959 (patch)
treec6c3383f5a8b8832f2365c216c33e5c929653836 /libhw
parent7ff738390e55d57a0f513c467a9da3b08c6902ab (diff)
Implement RP2040 hw alarms
Diffstat (limited to 'libhw')
-rw-r--r--libhw/CMakeLists.txt6
-rw-r--r--libhw/rp2040_bootclock.c13
-rw-r--r--libhw/rp2040_hwtimer.c120
-rw-r--r--libhw/rp2040_include/libhw/rp2040_hwalarm.h55
4 files changed, 178 insertions, 16 deletions
diff --git a/libhw/CMakeLists.txt b/libhw/CMakeLists.txt
index 47ffbdd..0dee422 100644
--- a/libhw/CMakeLists.txt
+++ b/libhw/CMakeLists.txt
@@ -12,15 +12,15 @@ target_link_libraries(libhw INTERFACE
if (PICO_PLATFORM STREQUAL "rp2040")
target_include_directories(libhw SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/rp2040_include)
target_sources(libhw INTERFACE
- rp2040_bootclock.c
+ rp2040_hwtimer.c
rp2040_hwspi.c
w5500.c
)
target_link_libraries(libhw INTERFACE
- pico_time
- hardware_timer
hardware_gpio
+ hardware_irq
hardware_spi
+ hardware_timer
)
endif()
diff --git a/libhw/rp2040_bootclock.c b/libhw/rp2040_bootclock.c
deleted file mode 100644
index 0484910..0000000
--- a/libhw/rp2040_bootclock.c
+++ /dev/null
@@ -1,13 +0,0 @@
-/* libhw/rp2040_bootclock.c - <libhw/generic/bootclock.h> implementation for pico-sdk
- *
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-#include <hardware/timer.h> /* pico-sdk:hardware_timer */
-
-#include <libhw/generic/bootclock.h>
-
-uint64_t bootclock_get_ns(void) {
- return time_us_64()*(NS_PER_S/US_PER_S);
-}
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();
+}
diff --git a/libhw/rp2040_include/libhw/rp2040_hwalarm.h b/libhw/rp2040_include/libhw/rp2040_hwalarm.h
new file mode 100644
index 0000000..3a7ae10
--- /dev/null
+++ b/libhw/rp2040_include/libhw/rp2040_hwalarm.h
@@ -0,0 +1,55 @@
+/* libhw/rp2040_hwalarm.h - Alarms for the RP2040
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBHW_RP2040_HWALARM_H_
+#define _LIBHW_RP2040_HWALARM_H_
+
+#include <stdbool.h> /* for bool */
+#include <stdint.h> /* for uint{n}_t */
+
+#include <libmisc/private.h> /* for BEGIN_PRIVATE, END_PRIVATE */
+
+/**
+ * The RP2040 has one system "timer" (which we also use for
+ * ./rp2040_bootclock.c) with 4 alarm interrupts.
+ */
+enum rp2040_hwalarm_instance {
+ RP2040_HWALARM_0 = 0,
+ RP2040_HWALARM_1 = 1,
+ RP2040_HWALARM_2 = 2,
+ RP2040_HWALARM_3 = 3,
+ _RP2040_HWALARM_NUM,
+};
+
+struct rp2040_hwalarm_trigger;
+struct rp2040_hwalarm_trigger {
+ BEGIN_PRIVATE(LIBHW_RP2040_HWALARM_H)
+ void *alarm;
+ struct rp2040_hwalarm_trigger *prev, *next;
+
+ uint64_t fire_at_us;
+ void (*cb)(void *);
+ void *cb_arg;
+ END_PRIVATE(LIBHW_RP2040_HWALARM_H)
+};
+
+/**
+ * Returns true on error.
+ *
+ * fire_at_ns must be at most UINT32_MAX µs (72 minutes) in the future
+ * (or an error will be returned).
+ *
+ * If fire_at_ns is in the past, then it will fire immediately.
+ */
+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);
+
+void rp2040_hwalarm_trigger_cancel(struct rp2040_hwalarm_trigger *self);
+
+#endif /* _LIBHW_RP2040_HWALARM_H_ */