diff options
author | Luke T. Shumaker <lukeshu@lukeshu.com> | 2024-10-29 11:42:22 -0600 |
---|---|---|
committer | Luke T. Shumaker <lukeshu@lukeshu.com> | 2024-10-29 11:42:22 -0600 |
commit | bcaf0bc655df337bc02c5dd78bfc3f4487103959 (patch) | |
tree | c6c3383f5a8b8832f2365c216c33e5c929653836 | |
parent | 7ff738390e55d57a0f513c467a9da3b08c6902ab (diff) |
Implement RP2040 hw alarms
-rw-r--r-- | libhw/CMakeLists.txt | 6 | ||||
-rw-r--r-- | libhw/rp2040_bootclock.c | 13 | ||||
-rw-r--r-- | libhw/rp2040_hwtimer.c | 120 | ||||
-rw-r--r-- | libhw/rp2040_include/libhw/rp2040_hwalarm.h | 55 |
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_ */ |