1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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();
}
|