blob: e4d7b9425fb07dc521913ba07b908b82c8d49264 (
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
|
// AVR timer interrupt scheduling code.
//
// Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include <avr/interrupt.h> // TCNT1
#include "autoconf.h" // CONFIG_AVR_CLKPR
#include "board/misc.h" // timer_from_us
#include "command.h" // shutdown
#include "irq.h" // irq_save
#include "sched.h" // sched_timer_kick
/****************************************************************
* Low level timer code
****************************************************************/
// Return the number of clock ticks for a given number of microseconds
uint32_t
timer_from_us(uint32_t us)
{
return us * (F_CPU / 1000000);
}
static inline uint16_t
timer_get(void)
{
return TCNT1;
}
static inline void
timer_set(uint16_t next)
{
OCR1A = next;
}
static inline void
timer_set_clear(uint16_t next)
{
OCR1A = next;
TIFR1 = 1<<OCF1A;
}
ISR(TIMER1_COMPA_vect)
{
sched_timer_kick();
}
static void
timer_init(void)
{
if (CONFIG_AVR_CLKPR != -1 && (uint8_t)CONFIG_AVR_CLKPR != CLKPR) {
// Program the clock prescaler
irqstatus_t flag = irq_save();
CLKPR = 0x80;
CLKPR = CONFIG_AVR_CLKPR;
irq_restore(flag);
}
// no outputs
TCCR1A = 0;
// Normal Mode
TCCR1B = 1<<CS10;
// enable interrupt
TIMSK1 = 1<<OCIE1A;
}
DECL_INIT(timer_init);
/****************************************************************
* 32bit timer wrappers
****************************************************************/
static uint32_t timer_last;
// Return the 32bit current time given the 16bit current time.
static __always_inline uint32_t
calc_time(uint32_t last, uint16_t cur)
{
union u32_u16_u {
struct { uint16_t lo, hi; };
uint32_t val;
} calc;
calc.val = last;
if (cur < calc.lo)
calc.hi++;
calc.lo = cur;
return calc.val;
}
// Called by main code once every millisecond. (IRQs disabled.)
void
timer_periodic(void)
{
timer_last = calc_time(timer_last, timer_get());
}
// Return the current time (in absolute clock ticks).
uint32_t
timer_read_time(void)
{
irqstatus_t flag = irq_save();
uint16_t cur = timer_get();
uint32_t last = timer_last;
irq_restore(flag);
return calc_time(last, cur);
}
#define TIMER_MIN_TICKS 100
// Set the next timer wake time (in absolute clock ticks). Caller
// must disable irqs. The caller should not schedule a time more than
// a few milliseconds in the future.
uint8_t
timer_set_next(uint32_t next)
{
uint16_t cur = timer_get();
if ((int16_t)(OCR1A - cur) < 0 && !(TIFR1 & (1<<OCF1A)))
// Already processing timer irqs
try_shutdown("timer_set_next called during timer dispatch");
uint32_t mintime = calc_time(timer_last, cur + TIMER_MIN_TICKS);
if (sched_is_before(mintime, next)) {
timer_set_clear(next);
return 0;
}
timer_set_clear(mintime);
return 1;
}
static uint8_t timer_repeat;
#define TIMER_MAX_REPEAT 40
#define TIMER_MAX_NEXT_REPEAT 15
#define TIMER_MIN_TRY_TICKS 60 // 40 ticks to exit irq; 20 ticks of progress
#define TIMER_DEFER_REPEAT_TICKS 200
// Similar to timer_set_next(), but wait for the given time if it is
// in the near future.
uint8_t
timer_try_set_next(uint32_t target)
{
uint16_t next = target, now = timer_get();
int16_t diff = next - now;
if (diff > TIMER_MIN_TRY_TICKS)
// Schedule next timer normally.
goto done;
// Next timer is in the past or near future - can't reschedule to it
uint8_t tr = timer_repeat-1;
if (likely(tr)) {
irq_enable();
timer_repeat = tr;
irq_disable();
while (diff >= 0) {
// Next timer is in the near future - wait for time to occur
now = timer_get();
irq_enable();
diff = next - now;
irq_disable();
}
return 0;
}
// Too many repeat timers from a single interrupt - force a pause
timer_repeat = TIMER_MAX_NEXT_REPEAT;
next = now + TIMER_DEFER_REPEAT_TICKS;
if (diff < (int16_t)(-timer_from_us(1000)))
goto fail;
done:
timer_set(next);
return 1;
fail:
shutdown("Rescheduled timer in the past");
}
static void
timer_task(void)
{
timer_repeat = TIMER_MAX_REPEAT;
}
DECL_TASK(timer_task);
|