summaryrefslogtreecommitdiff
path: root/src/oracle/timg_ref.c
blob: 38bccc8cfc3c6cc0e1694dff0c10a32784eafc8c (plain) (blame)
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/* The reference implementation for the timer groups and their watchdogs, which is ESP-IDF's own.
 *
 * Thin external-linkage wrappers over `timer_ll.h`, `mwdt_ll.h` and `timg_ll.h`, so Zig can call
 * IDF's `static inline` functions and the differential harness can run both implementations in one
 * image on one boot. There is no logic here: anything clever would be a third implementation to
 * doubt.
 *
 * Two things about the watchdog wrappers are deliberate. The write-protect dance is *inside* each
 * wrapper (`mwdt_ll_write_protect_disable` ... `mwdt_ll_write_protect_enable`) because that is what
 * IDF's callers do - `mwdt_ll_config_stage` itself will silently do nothing if protection is on -
 * and because our side does the same thing through `timg.unlock`/`release`. The two sides have to be
 * the same operation, key register included, or comparing WDTWPROTECT afterwards means nothing.
 * And nothing here ever calls `mwdt_ll_enable` on group 0: TIMG0 hosts the watchdog the rest of the
 * system depends on not firing.
 */

/* IDF's clock and reset LL functions are shadowed by a wrapper macro referencing
 * `__DECLARE_RCC_ATOMIC_ENV` / `__DECLARE_RCC_RC_ATOMIC_ENV`, identifiers IDF never defines
 * anywhere: their purpose is to make an unguarded call fail to compile, because the only legal
 * caller holds a FreeRTOS spinlock. There is no FreeRTOS here and core 1 is held in reset at
 * power-on, so declaring the names is exactly as safe as the spinlock would be - and it is what
 * IDF's own bootloader does (bootloader_support/src/bootloader_console.c:53). */
static int __DECLARE_RCC_ATOMIC_ENV __attribute__((unused));
static int __DECLARE_RCC_RC_ATOMIC_ENV __attribute__((unused));

#include "hal/timer_ll.h"
#include "hal/mwdt_ll.h"
#include "hal/timg_ll.h"
#include "soc/timer_group_struct.h"

static timg_dev_t *grp(int group)
{
    return TIMER_LL_GET_HW(group);
}

/* ------------------------------------------------------------------ general purpose timer */

void oracle_timg_set_divider(int group, unsigned timer, unsigned divider)
{
    timer_ll_set_clock_prescale(grp(group), timer, divider);
}

void oracle_timg_set_direction_up(int group, unsigned timer, int up)
{
    timer_ll_set_count_direction(grp(group), timer, up ? GPTIMER_COUNT_UP : GPTIMER_COUNT_DOWN);
}

void oracle_timg_set_auto_reload(int group, unsigned timer, int en)
{
    timer_ll_enable_auto_reload(grp(group), timer, en != 0);
}

void oracle_timg_enable_counter(int group, unsigned timer, int en)
{
    timer_ll_enable_counter(grp(group), timer, en != 0);
}

void oracle_timg_enable_alarm(int group, unsigned timer, int en)
{
    timer_ll_enable_alarm(grp(group), timer, en != 0);
}

void oracle_timg_set_alarm_value(int group, unsigned timer, unsigned long long value)
{
    timer_ll_set_alarm_value(grp(group), timer, value);
}

void oracle_timg_set_reload_value(int group, unsigned timer, unsigned long long value)
{
    timer_ll_set_reload_value(grp(group), timer, value);
}

unsigned long long oracle_timg_get_reload_value(int group, unsigned timer)
{
    return timer_ll_get_reload_value(grp(group), timer);
}

void oracle_timg_trigger_soft_reload(int group, unsigned timer)
{
    timer_ll_trigger_soft_reload(grp(group), timer);
}

/* The latch-then-read sequence: `timer_ll_trigger_soft_capture` writes TxUPDATE and spins until the
 * hardware clears it, and only then is the TxHI/TxLO pair meaningful. Exposed as one call because
 * that is how our `timg.read` expresses it, and splitting it would compare halves of a sequence. */
unsigned long long oracle_timg_read_counter(int group, unsigned timer)
{
    timer_ll_trigger_soft_capture(grp(group), timer);
    return timer_ll_get_counter_value(grp(group), timer);
}

void oracle_timg_set_clock_source_xtal(int group, unsigned timer)
{
    timer_ll_set_clock_source(group, timer, GPTIMER_CLK_SRC_XTAL);
}

void oracle_timg_set_clock_source_pll80m(int group, unsigned timer)
{
    timer_ll_set_clock_source(group, timer, GPTIMER_CLK_SRC_PLL_F80M);
}

void oracle_timg_enable_timer_clock(int group, unsigned timer, int en)
{
    timer_ll_enable_clock(group, timer, en != 0);
}

void oracle_timg_enable_bus_clock(int group, int en)
{
    timg_ll_enable_bus_clock(group, en != 0);
}

/* Pulses the group's reset bit and then clears WDT_FLASHBOOT_MOD_EN, which the reset re-arms
 * (timg_ll.h:51-71). The clearing is the interesting half: leave it out and the board reboots a
 * moment later with nothing on the console to explain it. */
void oracle_timg_reset_register(int group)
{
    timg_ll_reset_register(group);
}

/* --------------------------------------------------------------------------------- watchdog */

void oracle_mwdt_set_stage(int group, unsigned stage, unsigned timeout, unsigned action)
{
    mwdt_ll_write_protect_disable(grp(group));
    mwdt_ll_config_stage(grp(group), (wdt_stage_t)stage, timeout, (wdt_stage_action_t)action);
    mwdt_ll_write_protect_enable(grp(group));
}

void oracle_mwdt_disable_stage(int group, unsigned stage)
{
    mwdt_ll_write_protect_disable(grp(group));
    mwdt_ll_disable_stage(grp(group), stage);
    mwdt_ll_write_protect_enable(grp(group));
}

void oracle_mwdt_set_prescaler(int group, unsigned prescaler)
{
    mwdt_ll_write_protect_disable(grp(group));
    mwdt_ll_set_prescaler(grp(group), prescaler);
    mwdt_ll_write_protect_enable(grp(group));
}

void oracle_mwdt_set_cpu_reset_length(int group, unsigned length)
{
    mwdt_ll_write_protect_disable(grp(group));
    mwdt_ll_set_cpu_reset_length(grp(group), (wdt_reset_sig_length_t)length);
    mwdt_ll_write_protect_enable(grp(group));
}

void oracle_mwdt_set_sys_reset_length(int group, unsigned length)
{
    mwdt_ll_write_protect_disable(grp(group));
    mwdt_ll_set_sys_reset_length(grp(group), (wdt_reset_sig_length_t)length);
    mwdt_ll_write_protect_enable(grp(group));
}

void oracle_mwdt_set_flashboot_en(int group, int en)
{
    mwdt_ll_write_protect_disable(grp(group));
    mwdt_ll_set_flashboot_en(grp(group), en != 0);
    mwdt_ll_write_protect_enable(grp(group));
}

void oracle_mwdt_set_enabled(int group, int en)
{
    mwdt_ll_write_protect_disable(grp(group));
    if (en) {
        mwdt_ll_enable(grp(group));
    } else {
        mwdt_ll_disable(grp(group));
    }
    mwdt_ll_write_protect_enable(grp(group));
}

void oracle_mwdt_feed(int group)
{
    mwdt_ll_write_protect_disable(grp(group));
    mwdt_ll_feed(grp(group));
    mwdt_ll_write_protect_enable(grp(group));
}

/* The two halves of the protection dance on their own, so a case can check that our key value and
 * IDF's are the same word rather than only that a guarded sequence ends up locked. */
void oracle_mwdt_write_protect_disable(int group)
{
    mwdt_ll_write_protect_disable(grp(group));
}

void oracle_mwdt_write_protect_enable(int group)
{
    mwdt_ll_write_protect_enable(grp(group));
}

int oracle_mwdt_is_enabled(int group)
{
    return mwdt_ll_check_if_enabled(grp(group)) ? 1 : 0;
}