summaryrefslogtreecommitdiff
path: root/src/hal/clkrst.zig
blob: 89a27ef4c073c42ccde5d81d261f7dfa01465534 (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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
//! Peripheral clock gates and resets: HP_SYS_CLKRST.
//!
//! Two things about this block are counter-intuitive on the ESP32-P4, and both were found by
//! reading ESP-IDF rather than by assuming:
//!
//! **Peripheral clocks are already on.** `esp_system/port/soc/esp32p4/clk.c:200` says so in as many
//! words - "All peripheral clocks are default enabled after chip is powered on" - and the reset
//! values in `hp_sys_clkrst_reg.h` agree: REG_UART0_APB_CLK_EN, REG_TIMERGRP0_APB_CLK_EN,
//! REG_SYSTIMER_APB_CLK_EN and REG_IOMUX_APB_CLK_EN all default to 1, with their RST_EN bits at 0.
//! An image that boots from the stock second-stage bootloader never runs `esp_perip_clk_init`, so it
//! inherits those defaults. So this file is not a prerequisite for touching a peripheral; it is what
//! you need to *re*-initialise one, and to reach the few blocks that really are gated off (TWAI is
//! the notable one: REG_TWAI0_APB_CLK_EN defaults to 0).
//!
//! **The hazard is atomicity, not gating.** Every gate and reset bit for the whole chip lives in a
//! handful of shared registers, so `enable(.uart0)` is a read-modify-write of a word that also holds
//! the gate for unrelated peripherals. ESP-IDF makes unguarded calls impossible to compile by
//! referencing `__DECLARE_RCC_ATOMIC_ENV`, an identifier it never defines anywhere; the only legal
//! callers are inside `PERIPH_RCC_ATOMIC()`, which takes a FreeRTOS spinlock. There is no FreeRTOS
//! here and core 1 is held in reset at power-on (`hp_sys_clkrst_reg.h`: REG_RST_EN_CORE1_GLOBAL
//! defaults to 1), so masking interrupts around the read-modify-write is sufficient and is what
//! `atomically` does.

const std = @import("std");
const regs = @import("regs");
const mmio = @import("mmio");

const Reg = mmio.Reg;
const Field = mmio.Field;

// The four shared registers this file touches. Which field lives in which register is not derivable
// from the macro names - `HP_SYS_CLKRST_REG_UART0_APB_CLK_EN_S` does not say `SOC_CLK_CTRL2` - so the
// pairing is taken from ESP-IDF's own LL, cited per peripheral below.
const soc_clk_ctrl1 = Reg.at(regs.HP_SYS_CLKRST_SOC_CLK_CTRL1_REG);
const soc_clk_ctrl2 = Reg.at(regs.HP_SYS_CLKRST_SOC_CLK_CTRL2_REG);
const soc_clk_ctrl3 = Reg.at(regs.HP_SYS_CLKRST_SOC_CLK_CTRL3_REG);
/// SDMMC's reset bit is not in HP_SYS_CLKRST at all. `sdmmc_ll_reset_register`
/// (`sdmmc_ll.h:158-163`) writes `LP_AON_CLKRST.hp_sdmmc_emac_rst_ctrl.rst_en_sdmmc`, a register
/// of the *low-power* always-on clock-and-reset block, which it shares with the Ethernet MAC. So
/// the `Gates.reset` field is a register as well as a bit, and this is the row that proves it has
/// to be.
const lp_hp_sdmmc_emac_rst_ctrl = Reg.at(regs.LP_CLKRST_HP_SDMMC_EMAC_RST_CTRL_REG);
const hp_rst_en1 = Reg.at(regs.HP_SYS_CLKRST_HP_RST_EN1_REG);

/// Interrupts masked for the duration of a read-modify-write on a shared register:
///
///     const guard = clkrst.maskInterrupts();
///     defer guard.release();
///
/// mstatus.MIE is bit 3. `csrrc` clears it and returns the previous mstatus in one instruction, and
/// `release` restores only what was actually there - so this composes: using it inside code that
/// already had interrupts off does not turn them on at the end.
pub const Guard = struct {
    prev_mie: bool,

    pub inline fn release(self: Guard) void {
        if (self.prev_mie) {
            asm volatile ("csrs mstatus, %[mask]"
                :
                : [mask] "r" (@as(u32, 1 << 3)),
            );
        }
    }
};

pub inline fn maskInterrupts() Guard {
    const prev = asm volatile ("csrrc %[out], mstatus, %[mask]"
        : [out] "=r" (-> u32),
        : [mask] "r" (@as(u32, 1 << 3)),
    );
    return .{ .prev_mie = prev & (1 << 3) != 0 };
}

/// A peripheral's clock gates and reset bit.
///
/// `sys_clk` is present only where the peripheral has a second gate on the SYS clock as well as the
/// APB one; UART has both (uart_ll.h:252-253 reads `soc_clk_ctrl2.reg_uart0_apb_clk_en` and
/// `soc_clk_ctrl1.reg_uart0_sys_clk_en`), most blocks have only APB.
const Gates = struct {
    apb_clk: ?struct { reg: Reg, field: Field } = null,
    sys_clk: ?struct { reg: Reg, field: Field } = null,
    reset: struct { reg: Reg, field: Field },
    /// TIMG only: resetting the block re-arms flash-boot protection, which reboots the board a
    /// moment later with no diagnostic. `timg_ll.h:53-72` documents it and clears the bit as part of
    /// the reset; anything that resets TIMG must do the same.
    clears_flashboot: bool = false,
};

pub const Peripheral = enum {
    uart0,
    uart1,
    uart2,
    uart3,
    uart4,
    timg0,
    timg1,
    systimer,
    twai0,
    ledc,
    i2c0,
    i2c1,
    sdmmc,

    fn gates(comptime self: Peripheral) Gates {
        return switch (self) {
            // uart_ll.h:251-253 for UART0, and the same three fields per instance after it.
            .uart0 => .{
                .apb_clk = .{ .reg = soc_clk_ctrl2, .field = Field.of(regs.HP_SYS_CLKRST_REG_UART0_APB_CLK_EN_S, regs.HP_SYS_CLKRST_REG_UART0_APB_CLK_EN_V) },
                .sys_clk = .{ .reg = soc_clk_ctrl1, .field = Field.of(regs.HP_SYS_CLKRST_REG_UART0_SYS_CLK_EN_S, regs.HP_SYS_CLKRST_REG_UART0_SYS_CLK_EN_V) },
                .reset = .{ .reg = hp_rst_en1, .field = Field.of(regs.HP_SYS_CLKRST_REG_RST_EN_UART0_APB_S, regs.HP_SYS_CLKRST_REG_RST_EN_UART0_APB_V) },
            },
            .uart1 => .{
                .apb_clk = .{ .reg = soc_clk_ctrl2, .field = Field.of(regs.HP_SYS_CLKRST_REG_UART1_APB_CLK_EN_S, regs.HP_SYS_CLKRST_REG_UART1_APB_CLK_EN_V) },
                .sys_clk = .{ .reg = soc_clk_ctrl1, .field = Field.of(regs.HP_SYS_CLKRST_REG_UART1_SYS_CLK_EN_S, regs.HP_SYS_CLKRST_REG_UART1_SYS_CLK_EN_V) },
                .reset = .{ .reg = hp_rst_en1, .field = Field.of(regs.HP_SYS_CLKRST_REG_RST_EN_UART1_APB_S, regs.HP_SYS_CLKRST_REG_RST_EN_UART1_APB_V) },
            },
            .uart2 => .{
                .apb_clk = .{ .reg = soc_clk_ctrl2, .field = Field.of(regs.HP_SYS_CLKRST_REG_UART2_APB_CLK_EN_S, regs.HP_SYS_CLKRST_REG_UART2_APB_CLK_EN_V) },
                .sys_clk = .{ .reg = soc_clk_ctrl1, .field = Field.of(regs.HP_SYS_CLKRST_REG_UART2_SYS_CLK_EN_S, regs.HP_SYS_CLKRST_REG_UART2_SYS_CLK_EN_V) },
                .reset = .{ .reg = hp_rst_en1, .field = Field.of(regs.HP_SYS_CLKRST_REG_RST_EN_UART2_APB_S, regs.HP_SYS_CLKRST_REG_RST_EN_UART2_APB_V) },
            },
            .uart3 => .{
                .apb_clk = .{ .reg = soc_clk_ctrl2, .field = Field.of(regs.HP_SYS_CLKRST_REG_UART3_APB_CLK_EN_S, regs.HP_SYS_CLKRST_REG_UART3_APB_CLK_EN_V) },
                .sys_clk = .{ .reg = soc_clk_ctrl1, .field = Field.of(regs.HP_SYS_CLKRST_REG_UART3_SYS_CLK_EN_S, regs.HP_SYS_CLKRST_REG_UART3_SYS_CLK_EN_V) },
                .reset = .{ .reg = hp_rst_en1, .field = Field.of(regs.HP_SYS_CLKRST_REG_RST_EN_UART3_APB_S, regs.HP_SYS_CLKRST_REG_RST_EN_UART3_APB_V) },
            },
            .uart4 => .{
                .apb_clk = .{ .reg = soc_clk_ctrl2, .field = Field.of(regs.HP_SYS_CLKRST_REG_UART4_APB_CLK_EN_S, regs.HP_SYS_CLKRST_REG_UART4_APB_CLK_EN_V) },
                .sys_clk = .{ .reg = soc_clk_ctrl1, .field = Field.of(regs.HP_SYS_CLKRST_REG_UART4_SYS_CLK_EN_S, regs.HP_SYS_CLKRST_REG_UART4_SYS_CLK_EN_V) },
                .reset = .{ .reg = hp_rst_en1, .field = Field.of(regs.HP_SYS_CLKRST_REG_RST_EN_UART4_APB_S, regs.HP_SYS_CLKRST_REG_RST_EN_UART4_APB_V) },
            },
            // timg_ll.h:35-42 for the gate, :60-72 for the reset. The timer groups' APB gate is in
            // SOC_CLK_CTRL2 - the same word as the UARTs' - not in PERI_CLK_CTRL21. An earlier
            // version of this table had these four entries in PERI_CLK_CTRL21 and so wrote bits
            // 21-24 of an unrelated register; hp_sys_clkrst_reg.h:605 defines SOC_CLK_CTRL2_REG and
            // :753/:763/:770/:777 put TIMERGRP0 at bit 21, TIMERGRP1 at 22, SYSTIMER at 23 and
            // TWAI0 at 24 inside it. PERI_CLK_CTRL20/21 do hold timer-group fields - the per-timer
            // clock source and gate, see hal/timg.zig - which is what made the mix-up plausible.
            //
            // It survived a hardware check because `isClockEnabled` read back the same wrong bit
            // `setClockEnabled` had just written: self-consistent, and independent of the chip.
            .timg0 => .{
                .apb_clk = .{ .reg = soc_clk_ctrl2, .field = Field.of(regs.HP_SYS_CLKRST_REG_TIMERGRP0_APB_CLK_EN_S, regs.HP_SYS_CLKRST_REG_TIMERGRP0_APB_CLK_EN_V) },
                .reset = .{ .reg = hp_rst_en1, .field = Field.of(regs.HP_SYS_CLKRST_REG_RST_EN_TIMERGRP0_S, regs.HP_SYS_CLKRST_REG_RST_EN_TIMERGRP0_V) },
                .clears_flashboot = true,
            },
            .timg1 => .{
                .apb_clk = .{ .reg = soc_clk_ctrl2, .field = Field.of(regs.HP_SYS_CLKRST_REG_TIMERGRP1_APB_CLK_EN_S, regs.HP_SYS_CLKRST_REG_TIMERGRP1_APB_CLK_EN_V) },
                .reset = .{ .reg = hp_rst_en1, .field = Field.of(regs.HP_SYS_CLKRST_REG_RST_EN_TIMERGRP1_S, regs.HP_SYS_CLKRST_REG_RST_EN_TIMERGRP1_V) },
                .clears_flashboot = true,
            },
            // systimer_ll.h:71-72.
            .systimer => .{
                .apb_clk = .{ .reg = soc_clk_ctrl2, .field = Field.of(regs.HP_SYS_CLKRST_REG_SYSTIMER_APB_CLK_EN_S, regs.HP_SYS_CLKRST_REG_SYSTIMER_APB_CLK_EN_V) },
                .reset = .{ .reg = hp_rst_en1, .field = Field.of(regs.HP_SYS_CLKRST_REG_RST_EN_STIMER_S, regs.HP_SYS_CLKRST_REG_RST_EN_STIMER_V) },
            },
            // The one block whose clock is gated OFF at power-on, which makes it the only peripheral
            // where `enable` is observably necessary rather than merely correct.
            .twai0 => .{
                .apb_clk = .{ .reg = soc_clk_ctrl2, .field = Field.of(regs.HP_SYS_CLKRST_REG_TWAI0_APB_CLK_EN_S, regs.HP_SYS_CLKRST_REG_TWAI0_APB_CLK_EN_V) },
                .reset = .{ .reg = hp_rst_en1, .field = Field.of(regs.HP_SYS_CLKRST_REG_RST_EN_TWAI0_S, regs.HP_SYS_CLKRST_REG_RST_EN_TWAI0_V) },
            },
            // ledc_ll.h:135 for the gate (`HP_SYS_CLKRST.soc_clk_ctrl3.reg_ledc_apb_clk_en`) and
            // :150 for the reset (`hp_rst_en1.reg_rst_en_ledc`). LEDC's APB gate is the *first* bit
            // of SOC_CLK_CTRL3, a third register this table did not previously need, and it is one
            // of the few whose reset value is 0 (hp_sys_clkrst_reg.h:835): LEDC's registers are
            // gated off at power-on, so `setClockEnabled(.ledc, true)` is a prerequisite and not a
            // formality. LEDC's *function* clock and its source mux live in PERI_CLK_CTRL22
            // (ledc_ll.h:179, :241) and belong to the peripheral, not to this table - see
            // hal/ledc.zig.
            .ledc => .{
                .apb_clk = .{ .reg = soc_clk_ctrl3, .field = Field.of(regs.HP_SYS_CLKRST_REG_LEDC_APB_CLK_EN_S, regs.HP_SYS_CLKRST_REG_LEDC_APB_CLK_EN_V) },
                .reset = .{ .reg = hp_rst_en1, .field = Field.of(regs.HP_SYS_CLKRST_REG_RST_EN_LEDC_S, regs.HP_SYS_CLKRST_REG_RST_EN_LEDC_V) },
            },
            // i2c_ll.h:149-156 for the gates (`HP_SYS_CLKRST.soc_clk_ctrl2.reg_i2c0_apb_clk_en`,
            // and `reg_i2c1_apb_clk_en` for port 1) and :167-176 for the resets
            // (`hp_rst_en1.reg_rst_en_i2c0` / `_i2c1`). Both APB gates default to 1
            // (hp_sys_clkrst_reg.h:694-703), so the registers are reachable from boot; what I2C
            // does *not* get from this table is its controller clock, whose enable, source mux and
            // divider are I2C-specific fields of PERI_CLK_CTRL10/11 and live in hal/i2c.zig. That
            // one defaults to 0, so an I2C port brought up through this table alone has readable
            // registers and a state machine that never moves.
            .i2c0 => .{
                .apb_clk = .{ .reg = soc_clk_ctrl2, .field = Field.of(regs.HP_SYS_CLKRST_REG_I2C0_APB_CLK_EN_S, regs.HP_SYS_CLKRST_REG_I2C0_APB_CLK_EN_V) },
                .reset = .{ .reg = hp_rst_en1, .field = Field.of(regs.HP_SYS_CLKRST_REG_RST_EN_I2C0_S, regs.HP_SYS_CLKRST_REG_RST_EN_I2C0_V) },
            },
            .i2c1 => .{
                .apb_clk = .{ .reg = soc_clk_ctrl2, .field = Field.of(regs.HP_SYS_CLKRST_REG_I2C1_APB_CLK_EN_S, regs.HP_SYS_CLKRST_REG_I2C1_APB_CLK_EN_V) },
                .reset = .{ .reg = hp_rst_en1, .field = Field.of(regs.HP_SYS_CLKRST_REG_RST_EN_I2C1_S, regs.HP_SYS_CLKRST_REG_RST_EN_I2C1_V) },
            },
            // The one row in this table whose two halves live in two different peripherals, and
            // the one whose clock really is gated off at power-on alongside LEDC's.
            //
            // `sdmmc_ll.h:140-144` is the gate: `HP_SYS_CLKRST.soc_clk_ctrl1.reg_sdmmc_sys_clk_en`,
            // a *SYS* clock and not an APB one - SDMMC has no APB gate at all, which is why the
            // `apb_clk` field is absent here rather than merely unused. It defaults to 0
            // (hp_sys_clkrst_reg.h:475-481, "default: 0"), so `setClockEnabled(.sdmmc, true)` is a
            // prerequisite for the register block reading anything but stale values.
            //
            // `sdmmc_ll.h:158-163` is the reset, and it is in LP_AON_CLKRST:
            // `hp_sdmmc_emac_rst_ctrl.rst_en_sdmmc`, bit 28 (lp_clkrst_reg.h:993-999). Looking for
            // an `HP_SYS_CLKRST_REG_RST_EN_SDMMC` finds nothing, which is exactly the shape of the
            // mistake the timer-group rows above record: a plausible name in the wrong register.
            //
            // The host clock generator - source mux, divider, sampling phase - is *not* here. It
            // is SDMMC-specific and lives in PERI_CLK_CTRL01/02, in hal/sdmmc.zig, the same
            // division this table makes for I2C and LEDC.
            .sdmmc => .{
                .sys_clk = .{ .reg = soc_clk_ctrl1, .field = Field.of(regs.HP_SYS_CLKRST_REG_SDMMC_SYS_CLK_EN_S, regs.HP_SYS_CLKRST_REG_SDMMC_SYS_CLK_EN_V) },
                .reset = .{ .reg = lp_hp_sdmmc_emac_rst_ctrl, .field = Field.of(regs.LP_CLKRST_RST_EN_SDMMC_S, regs.LP_CLKRST_RST_EN_SDMMC_V) },
            },
        };
    }
};

/// Turn a peripheral's bus clocks on or off.
pub fn setClockEnabled(comptime p: Peripheral, on: bool) void {
    const g = comptime p.gates();
    const v: u32 = @intFromBool(on);
    const guard = maskInterrupts();
    defer guard.release();
    if (g.sys_clk) |s| s.reg.modify(.{s.field.is(v)});
    if (g.apb_clk) |a| a.reg.modify(.{a.field.is(v)});
}

/// Whether the peripheral's bus clock is on.
///
/// APB gate if it has one, SYS gate otherwise: SDMMC has only the latter (`sdmmc_ll.h:140-144`),
/// and answering `true` unconditionally for it would have made the oracle's clock check - the one
/// that exists because a gated block reads stale rather than zero - pass on a gated block.
pub fn isClockEnabled(comptime p: Peripheral) bool {
    const g = comptime p.gates();
    if (g.apb_clk) |a| return a.reg.get(a.field) == 1;
    if (g.sys_clk) |s| return s.reg.get(s.field) == 1;
    return true;
}

/// Pulse a peripheral's reset: assert, deassert.
///
/// For the timer groups this also clears flash-boot watchdog protection, which the reset re-arms.
/// Leaving that out reboots the board a moment later with nothing on the console to explain it.
pub fn resetPeripheral(comptime p: Peripheral) void {
    const g = comptime p.gates();
    {
        const guard = maskInterrupts();
        defer guard.release();
        g.reset.reg.modify(.{g.reset.field.is(1)});
        g.reset.reg.modify(.{g.reset.field.is(0)});
    }
    if (comptime g.clears_flashboot) {
        const wdtconfig0 = Reg.atAddress(switch (p) {
            .timg0 => regs.TIMG_WDTCONFIG0_REG(0),
            .timg1 => regs.TIMG_WDTCONFIG0_REG(1),
            else => unreachable,
        });
        wdtconfig0.modify(.{Field.of(regs.TIMG_WDT_FLASHBOOT_MOD_EN_S, regs.TIMG_WDT_FLASHBOOT_MOD_EN_V).is(0)});
    }
}

/// Reset a peripheral and make sure its clocks are on, in that order: a peripheral configured
/// before its reset is released loses the configuration.
pub fn init(comptime p: Peripheral) void {
    setClockEnabled(p, true);
    resetPeripheral(p);
}