/* 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(); }