summaryrefslogtreecommitdiff
path: root/libhw/rp2040_hwtimer.c
blob: b8255b81cea86396a59228c64c672095d9524c82 (plain)
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();
}