//! The timer groups: TIMG0 and TIMG1, each two general-purpose 54-bit timers plus one MWDT. //! //! Three unrelated functions share one register block (timg_ll.h:7 says so in as many words): //! the general-purpose timers, the main watchdog, and RTC clock calibration. Only the first two are //! here; calibration belongs to the clock tree, and ETM and interrupts are deliberately absent. //! //! Four things about this block cost real care, all of them taken from ESP-IDF's LL rather than //! guessed at: //! //! **Reading the counter is a sequence, not a load** (timer_ll.h:248-269). The counter lives in a //! different clock domain from the register file, so its value only appears in `TxLO`/`TxHI` after a //! software capture: //! //! write TIMG_TxUPDATE = 1 -> ask for a capture //! poll until TIMG_Tx_UPDATE == 0 -> the hardware clears it when the pair is latched //! read TxHI, then TxLO -> 22 bits + 32 bits = the 54-bit count //! //! Note the polarity: unlike SYSTIMER, which sets a separate `VALUE_VALID` bit, this peripheral //! *clears the request bit* to acknowledge. Waiting for it to become 1 hangs forever; not waiting at //! all returns whatever the last capture left, which for a never-captured timer is 0 and therefore //! looks like a stopped timer rather than like a bug. //! //! **The watchdog registers are write-protected, and the key is the reset value** (mwdt_ll.h:231-244 //! and timer_group_reg.h, TIMG_WDT_WKEY: "If the register contains a different value than its reset //! value, write protection is enabled", default 1356348065 = 0x50D83AA1). So "unlock" means writing //! the key back, and "lock" means writing anything else - IDF writes 0. A watchdog register write //! made while locked is silently dropped, which is the failure mode this file's API shape exists to //! prevent: every MWDT operation is a method on the `Watchdog` handle returned by `unlock`, and //! there is no way to reach one without holding it: //! //! const wdt = timg.unlock(.timg1); //! defer wdt.release(); //! wdt.setStage(.stage0, 2_000_000, .reset_system); //! //! **Watchdog configuration is committed asynchronously.** Every write to WDTCONFIG0-5 has to be //! followed by `WDT_CONF_UPDATE_EN` (mwdt_ll.h:122, and again after every other config write), which //! is a write-to-trigger bit. The exception is `WDT_EN` itself: `mwdt_ll_enable`/`_disable` //! (mwdt_ll.h:61-77) do *not* pulse it, so neither does `setEnabled` - matching IDF exactly matters //! more here than consistency, because the differential harness compares the resulting word. //! //! **Do not resurrect a watchdog you are not feeding.** TIMG0 hosts MWDT0, which this image's //! bootloader has already disabled, and `TIMG_WDT_FLASHBOOT_MOD_EN` defaults to 1 and runs the //! watchdog *independently of* `WDT_EN` (mwdt_ll.h:186-188). Resetting a timer group therefore //! re-arms flash-boot protection and reboots the board a moment later with nothing on the console to //! explain it; `clkrst.resetPeripheral` clears the bit as part of the reset for exactly this reason //! (its `clears_flashboot` flag), which is why nothing in this file pulses a reset bit itself. const std = @import("std"); const regs = @import("regs"); const mmio = @import("mmio"); const clkrst = @import("clkrst.zig"); const Reg = mmio.Reg; const Field = mmio.Field; /// TIMG_LL_INST_NUM (timg_ll.h:20). pub const group_count = 2; /// TIMG_LL_GPTIMERS_PER_INST (timg_ll.h:23). Two per group on the P4, unlike the C-series parts. pub const timers_per_group = 2; /// TIMER_LL_COUNTER_BIT_WIDTH (timer_ll.h:25). 32 bits in `TxLO` plus 22 in `TxHI`. pub const counter_bits = 54; pub const Group = enum(u1) { timg0 = 0, timg1 = 1 }; pub const Timer = enum(u1) { t0 = 0, t1 = 1 }; // -------------------------------------------------------------------------------- addressing // // The macros are indexed two different ways at once and neither is derivable from the other: // `TIMG_T0CONFIG_REG(i)` takes the *group*, while the *timer* is baked into the macro name // (`T0CONFIG` vs `T1CONFIG`). Rather than duplicate every accessor per timer, the timer index is // turned into a stride - but a stride assumed is a stride that eventually writes into the next // register, so both strides are checked at comptime against the macros for the other instance. const group_stride = mmio.addr(regs.TIMG_T0CONFIG_REG(1)) - mmio.addr(regs.TIMG_T0CONFIG_REG(0)); const timer_stride = mmio.addr(regs.TIMG_T1CONFIG_REG(0)) - mmio.addr(regs.TIMG_T0CONFIG_REG(0)); // Absolute addresses of group 0 / timer 0's registers. Every other (group, timer) is these plus a // multiple of the two strides. const a_config = mmio.addr(regs.TIMG_T0CONFIG_REG(0)); const a_lo = mmio.addr(regs.TIMG_T0LO_REG(0)); const a_hi = mmio.addr(regs.TIMG_T0HI_REG(0)); const a_update = mmio.addr(regs.TIMG_T0UPDATE_REG(0)); const a_alarm_lo = mmio.addr(regs.TIMG_T0ALARMLO_REG(0)); const a_alarm_hi = mmio.addr(regs.TIMG_T0ALARMHI_REG(0)); const a_load_lo = mmio.addr(regs.TIMG_T0LOADLO_REG(0)); const a_load_hi = mmio.addr(regs.TIMG_T0LOADHI_REG(0)); const a_load = mmio.addr(regs.TIMG_T0LOAD_REG(0)); comptime { // The timer sub-block is contiguous and uniform - assert it, per register, rather than trust // that 0x24 happens to be right for all nine. const pairs = .{ .{ a_config, mmio.addr(regs.TIMG_T1CONFIG_REG(0)) }, .{ a_lo, mmio.addr(regs.TIMG_T1LO_REG(0)) }, .{ a_hi, mmio.addr(regs.TIMG_T1HI_REG(0)) }, .{ a_update, mmio.addr(regs.TIMG_T1UPDATE_REG(0)) }, .{ a_alarm_lo, mmio.addr(regs.TIMG_T1ALARMLO_REG(0)) }, .{ a_alarm_hi, mmio.addr(regs.TIMG_T1ALARMHI_REG(0)) }, .{ a_load_lo, mmio.addr(regs.TIMG_T1LOADLO_REG(0)) }, .{ a_load_hi, mmio.addr(regs.TIMG_T1LOADHI_REG(0)) }, .{ a_load, mmio.addr(regs.TIMG_T1LOAD_REG(0)) }, }; for (pairs) |p| { if (p[1] - p[0] != timer_stride) @compileError( "the two timers' registers are not a uniform stride apart; index them per timer", ); } // And the group stride is the same for a register other than CONFIG. if (mmio.addr(regs.TIMG_T0LO_REG(1)) - a_lo != group_stride) @compileError("the two timer groups are not a uniform stride apart"); // T0's and T1's *fields* sit at the same bit positions in their respective registers, which is // what makes one set of Field constants enough. If a future register set moves one of them, // this stops the build instead of writing the divider into the alarm enable. const t1_divider = Field.of(regs.TIMG_T1_DIVIDER_S, regs.TIMG_T1_DIVIDER_V); const t1_en = Field.of(regs.TIMG_T1_EN_S, regs.TIMG_T1_EN_V); const t1_update = Field.of(regs.TIMG_T1_UPDATE_S, regs.TIMG_T1_UPDATE_V); const t1_hi = Field.of(regs.TIMG_T1_HI_S, regs.TIMG_T1_HI_V); if (t1_divider.shift != divider.shift or t1_divider.width != divider.width or t1_en.shift != counter_en.shift or t1_update.shift != update.shift or t1_hi.width != count_hi.width) @compileError("timer 0 and timer 1 disagree on field positions; look up fields per timer"); } inline fn tReg(comptime a0: u32, g: Group, t: Timer) Reg { return Reg.atAddress(a0 + group_stride * @as(u32, @intFromEnum(g)) + timer_stride * @as(u32, @intFromEnum(t))); } /// `a0` is not comptime: the stage-timeout registers are picked by a runtime `Stage` /// (`stageHoldAddr`), and every other caller passes a constant that folds anyway. inline fn gReg(a0: u32, g: Group) Reg { return Reg.atAddress(a0 + group_stride * @as(u32, @intFromEnum(g))); } // TxCONFIG fields. `divcnt_rst` is write-to-trigger; the rest are plain R/W. const alarm_en = Field.of(regs.TIMG_T0_ALARM_EN_S, regs.TIMG_T0_ALARM_EN_V); const divcnt_rst = Field.of(regs.TIMG_T0_DIVCNT_RST_S, regs.TIMG_T0_DIVCNT_RST_V); const divider = Field.of(regs.TIMG_T0_DIVIDER_S, regs.TIMG_T0_DIVIDER_V); const autoreload = Field.of(regs.TIMG_T0_AUTORELOAD_S, regs.TIMG_T0_AUTORELOAD_V); const increase = Field.of(regs.TIMG_T0_INCREASE_S, regs.TIMG_T0_INCREASE_V); const counter_en = Field.of(regs.TIMG_T0_EN_S, regs.TIMG_T0_EN_V); const update = Field.of(regs.TIMG_T0_UPDATE_S, regs.TIMG_T0_UPDATE_V); const count_hi = Field.of(regs.TIMG_T0_HI_S, regs.TIMG_T0_HI_V); const alarm_value_hi = Field.of(regs.TIMG_T0_ALARM_HI_S, regs.TIMG_T0_ALARM_HI_V); const load_value_hi = Field.of(regs.TIMG_T0_LOAD_HI_S, regs.TIMG_T0_LOAD_HI_V); // ------------------------------------------------------------------------------ timer clocks // // The timers' function clock is selected and gated in HP_SYS_CLKRST, not in the timer group: group 0 // in PERI_CLK_CTRL20 and group 1 in PERI_CLK_CTRL21 (timer_ll.h:117-129, :146-160). Two shared // registers, so both operations take the interrupt guard - the same read-modify-write hazard // `clkrst` exists for. const peri_clk_ctrl20 = Reg.at(regs.HP_SYS_CLKRST_PERI_CLK_CTRL20_REG); const peri_clk_ctrl21 = Reg.at(regs.HP_SYS_CLKRST_PERI_CLK_CTRL21_REG); /// The three function clocks a GP timer can run from, with the encodings from /// `timer_ll_set_clock_source` (timer_ll.h:100-116). The numbering is not the enum order anyone /// would pick: XTAL is 0, RC_FAST is 1, PLL_F80M is 2. pub const ClockSource = enum(u2) { xtal = 0, rc_fast = 1, pll_f80m = 2, }; /// Where the group/timer's source-select and gate fields live. Both are in one word per group, and /// the bit positions differ per timer, so this is a genuine per-instance lookup rather than a stride. const TimerClock = struct { reg: Reg, src_sel: Field, clk_en: Field, }; inline fn timerClock(comptime g: Group, comptime t: Timer) TimerClock { return switch (g) { .timg0 => switch (t) { .t0 => .{ .reg = peri_clk_ctrl20, .src_sel = Field.of(regs.HP_SYS_CLKRST_REG_TIMERGRP0_T0_SRC_SEL_S, regs.HP_SYS_CLKRST_REG_TIMERGRP0_T0_SRC_SEL_V), .clk_en = Field.of(regs.HP_SYS_CLKRST_REG_TIMERGRP0_T0_CLK_EN_S, regs.HP_SYS_CLKRST_REG_TIMERGRP0_T0_CLK_EN_V), }, .t1 => .{ .reg = peri_clk_ctrl20, .src_sel = Field.of(regs.HP_SYS_CLKRST_REG_TIMERGRP0_T1_SRC_SEL_S, regs.HP_SYS_CLKRST_REG_TIMERGRP0_T1_SRC_SEL_V), .clk_en = Field.of(regs.HP_SYS_CLKRST_REG_TIMERGRP0_T1_CLK_EN_S, regs.HP_SYS_CLKRST_REG_TIMERGRP0_T1_CLK_EN_V), }, }, .timg1 => switch (t) { .t0 => .{ .reg = peri_clk_ctrl21, .src_sel = Field.of(regs.HP_SYS_CLKRST_REG_TIMERGRP1_T0_SRC_SEL_S, regs.HP_SYS_CLKRST_REG_TIMERGRP1_T0_SRC_SEL_V), .clk_en = Field.of(regs.HP_SYS_CLKRST_REG_TIMERGRP1_T0_CLK_EN_S, regs.HP_SYS_CLKRST_REG_TIMERGRP1_T0_CLK_EN_V), }, .t1 => .{ .reg = peri_clk_ctrl21, .src_sel = Field.of(regs.HP_SYS_CLKRST_REG_TIMERGRP1_T1_SRC_SEL_S, regs.HP_SYS_CLKRST_REG_TIMERGRP1_T1_SRC_SEL_V), .clk_en = Field.of(regs.HP_SYS_CLKRST_REG_TIMERGRP1_T1_CLK_EN_S, regs.HP_SYS_CLKRST_REG_TIMERGRP1_T1_CLK_EN_V), }, }, }; } /// Select a timer's function clock. Comptime instance because the field pairing really does differ /// per (group, timer) - four different bit positions in two registers. pub fn setClockSource(comptime g: Group, comptime t: Timer, src: ClockSource) void { const c = comptime timerClock(g, t); const guard = clkrst.maskInterrupts(); defer guard.release(); c.reg.modify(.{c.src_sel.is(@intFromEnum(src))}); } /// The timer's function-clock gate, distinct from the group's bus clock in `clkrst`. Defaults to 1 /// at power-on (hp_sys_clkrst_reg.h: REG_TIMERGRP0_T0_CLK_EN default 1). pub fn setClockEnabled(comptime g: Group, comptime t: Timer, on: bool) void { const c = comptime timerClock(g, t); const guard = clkrst.maskInterrupts(); defer guard.release(); c.reg.modify(.{c.clk_en.is(@intFromBool(on))}); } // ------------------------------------------------------------------------ general purpose timer pub const Direction = enum { up, down }; /// Prescaler on the function clock. 2 is the smallest the hardware accepts and 65536 the largest, /// encoded as 0 (timer_ll.h:191-199). The divider counter is reset in a second store afterwards, /// exactly as IDF does it: without that the new divider only takes effect after the old one's /// current period ends, so the first tick after a change is the wrong length. pub fn setDivider(g: Group, t: Timer, div: u32) void { std.debug.assert(div >= 2 and div <= 65536); const cfg = tReg(a_config, g, t); cfg.modify(.{divider.is(if (div >= 65536) 0 else div)}); cfg.modify(.{divcnt_rst.is(1)}); } pub fn setDirection(g: Group, t: Timer, dir: Direction) void { tReg(a_config, g, t).modify(.{increase.is(@intFromBool(dir == .up))}); } /// Reload the counter from `TxLOADLO`/`TxLOADHI` automatically on every alarm. pub fn setAutoReload(g: Group, t: Timer, on: bool) void { tReg(a_config, g, t).modify(.{autoreload.is(@intFromBool(on))}); } pub fn setCounterEnabled(g: Group, t: Timer, on: bool) void { tReg(a_config, g, t).modify(.{counter_en.is(@intFromBool(on))}); } pub fn setAlarmEnabled(g: Group, t: Timer, on: bool) void { tReg(a_config, g, t).modify(.{alarm_en.is(@intFromBool(on))}); } /// The 54-bit alarm value. Low word first would be equally correct - the comparator only sees the /// pair - but IDF writes high then low (timer_ll.h:279-283) and matching its order keeps the write /// trace comparable. pub fn setAlarmValue(g: Group, t: Timer, value: u64) void { tReg(a_alarm_hi, g, t).modify(.{alarm_value_hi.is(@truncate(value >> 32))}); tReg(a_alarm_lo, g, t).writeRaw(@truncate(value)); } /// The value a reload puts into the counter, whether triggered by `load` or by an auto-reload. pub fn setLoadValue(g: Group, t: Timer, value: u64) void { tReg(a_load_hi, g, t).modify(.{load_value_hi.is(@truncate(value >> 32))}); tReg(a_load_lo, g, t).writeRaw(@truncate(value)); } pub fn getLoadValue(g: Group, t: Timer) u64 { const hi: u64 = tReg(a_load_hi, g, t).get(load_value_hi); return (hi << 32) | tReg(a_load_lo, g, t).raw(); } /// Copy the load value into the counter now. `TIMG_TxLOAD_REG` is a whole-word write-to-trigger /// register: the value written is irrelevant, so this is a bare store rather than a field write. pub fn load(g: Group, t: Timer) void { tReg(a_load, g, t).writeRaw(1); } /// The counter, through the capture handshake described at the top of this file. /// /// Returns null rather than spinning forever if the peripheral never acknowledges: with the group's /// bus clock gated off, or the timer's function clock gated off, `UPDATE` never clears, and hanging /// inside a HAL call with no output is the worst possible way to report that. The bound is the same /// 10,000 reads `systimer.read` uses. pub fn read(g: Group, t: Timer) ?u64 { const upd = tReg(a_update, g, t); // Ask for a capture. IDF assigns to the struct bitfield, which is a read-modify-write of a word // whose only other bits are reserved, so `modify` is both the honest operation and the one that // produces the same store. upd.modify(.{update.is(1)}); var spins: u32 = 0; while (upd.get(update) != 0) { spins += 1; if (spins > 10_000) return null; } const hi: u64 = tReg(a_hi, g, t).get(count_hi); return (hi << 32) | tReg(a_lo, g, t).raw(); } // ----------------------------------------------------------------------------------- watchdog const a_wdtconfig0 = mmio.addr(regs.TIMG_WDTCONFIG0_REG(0)); const a_wdtconfig1 = mmio.addr(regs.TIMG_WDTCONFIG1_REG(0)); const a_wdtconfig2 = mmio.addr(regs.TIMG_WDTCONFIG2_REG(0)); const a_wdtconfig3 = mmio.addr(regs.TIMG_WDTCONFIG3_REG(0)); const a_wdtconfig4 = mmio.addr(regs.TIMG_WDTCONFIG4_REG(0)); const a_wdtconfig5 = mmio.addr(regs.TIMG_WDTCONFIG5_REG(0)); const a_wdtfeed = mmio.addr(regs.TIMG_WDTFEED_REG(0)); const a_wdtwprotect = mmio.addr(regs.TIMG_WDTWPROTECT_REG(0)); const wdt_en = Field.of(regs.TIMG_WDT_EN_S, regs.TIMG_WDT_EN_V); const wdt_conf_update_en = Field.of(regs.TIMG_WDT_CONF_UPDATE_EN_S, regs.TIMG_WDT_CONF_UPDATE_EN_V); const wdt_flashboot_mod_en = Field.of(regs.TIMG_WDT_FLASHBOOT_MOD_EN_S, regs.TIMG_WDT_FLASHBOOT_MOD_EN_V); const wdt_cpu_reset_length = Field.of(regs.TIMG_WDT_CPU_RESET_LENGTH_S, regs.TIMG_WDT_CPU_RESET_LENGTH_V); const wdt_sys_reset_length = Field.of(regs.TIMG_WDT_SYS_RESET_LENGTH_S, regs.TIMG_WDT_SYS_RESET_LENGTH_V); const wdt_clk_prescale = Field.of(regs.TIMG_WDT_CLK_PRESCALE_S, regs.TIMG_WDT_CLK_PRESCALE_V); const wdt_divcnt_rst = Field.of(regs.TIMG_WDT_DIVCNT_RST_S, regs.TIMG_WDT_DIVCNT_RST_V); /// The write-protect key, and also `TIMG_WDT_WKEY`'s reset value: protection is on whenever the /// register holds anything *else* (timer_group_reg.h, TIMG_WDT_WKEY, default 1356348065). IDF's /// `mwdt_ll_write_protect_disable` writes this exact constant (mwdt_ll.h:243). pub const wkey: u32 = 0x50D8_3AA1; /// What IDF writes to re-enable protection (mwdt_ll.h:233). Any non-key value would do; using the /// same one keeps the register comparable against IDF's. const wkey_locked: u32 = 0; // The headers carry no reset-value macro to check `wkey` against - `TIMG_WDT_WKEY_V` is the field // mask, 0xffffffff - so the constant is copied from the two places that state it: the register // description's "default: 1356348065" and mwdt_ll.h:243's 0x50D83AA1. The unit test at the end of // this file pins those two against each other, which is the only check available without a chip. /// MWDT stages, each with its own timeout and its own action. Stage 0 fires first; a stage that is /// not fed escalates to the next. pub const Stage = enum(u2) { stage0 = 0, stage1 = 1, stage2 = 2, stage3 = 3 }; /// What a stage does when it expires (mwdt_ll.h:23-26). pub const Action = enum(u2) { off = 0, interrupt = 1, reset_cpu = 2, reset_system = 3, }; /// Length of the reset pulse a `reset_cpu`/`reset_system` stage asserts (mwdt_ll.h:28-35). pub const ResetLength = enum(u3) { ns_100 = 0, ns_200 = 1, ns_300 = 2, ns_400 = 3, ns_500 = 4, ns_800 = 5, us_1_6 = 6, us_3_2 = 7, }; /// A group's MWDT with write protection lifted, and the only way to reach an MWDT operation: /// /// const wdt = timg.unlock(.timg1); /// defer wdt.release(); /// wdt.setStage(.stage0, ticks, .reset_system); /// /// The handle exists because a watchdog register write made while protection is on is silently /// dropped - no fault, no status bit, just a watchdog that keeps its old timeout - and that is not a /// mistake worth making twice. pub const Watchdog = struct { group: Group, /// Re-enable write protection. Not idempotent-with-`unlock` in the composable sense that /// `clkrst.Guard` is: the hardware has one key register and no nesting count, so an inner /// `release` really does lock an outer caller out. There is nothing in this HAL that nests. pub inline fn release(self: Watchdog) void { gReg(a_wdtwprotect, self.group).writeRaw(wkey_locked); } /// WDTCONFIG0-5 are shadowed; the hardware only takes them at a `CONF_UPDATE_EN` pulse /// (mwdt_ll.h:121-122). Write-to-trigger, so this is a single deliberate store. inline fn commit(self: Watchdog) void { gReg(a_wdtconfig0, self.group).modify(.{wdt_conf_update_en.is(1)}); } inline fn config0(self: Watchdog) Reg { return gReg(a_wdtconfig0, self.group); } /// The stage's action bits and its timeout live in different registers - the action in /// WDTCONFIG0, the timeout in WDTCONFIG2+stage - which is why this takes both at once /// (mwdt_ll.h:98-123). `timeout` is in MWDT clock cycles, i.e. after the prescaler. pub fn setStage(self: Watchdog, stage: Stage, timeout: u32, action: Action) void { self.config0().modify(.{stageAction(stage).is(@intFromEnum(action))}); gReg(stageHoldAddr(stage), self.group).writeRaw(timeout); self.commit(); } /// Turn one stage off without disturbing its timeout (mwdt_ll.h:131-152). pub fn disableStage(self: Watchdog, stage: Stage) void { self.config0().modify(.{stageAction(stage).is(@intFromEnum(Action.off))}); self.commit(); } pub fn getStageTimeout(self: Watchdog, stage: Stage) u32 { return gReg(stageHoldAddr(stage), self.group).raw(); } /// Prescaler from the MWDT's source clock (XTAL on this chip - mwdt_ll.h:273-283 asserts it and /// selects nothing). 1 to 65535; IDF's default is 20000, which gives 500 ticks/us /// (mwdt_ll.h:20). pub fn setPrescaler(self: Watchdog, prescaler: u32) void { std.debug.assert(prescaler >= 1 and prescaler <= 0xffff); gReg(a_wdtconfig1, self.group).modify(.{wdt_clk_prescale.is(prescaler)}); self.commit(); } pub fn setCpuResetLength(self: Watchdog, len: ResetLength) void { self.config0().modify(.{wdt_cpu_reset_length.is(@intFromEnum(len))}); self.commit(); } pub fn setSysResetLength(self: Watchdog, len: ResetLength) void { self.config0().modify(.{wdt_sys_reset_length.is(@intFromEnum(len))}); self.commit(); } /// Flash-boot protection: a second, independent way for this watchdog to run. It ignores /// `WDT_EN` entirely (mwdt_ll.h:186-188), it defaults to 1, and a group reset re-arms it - so /// clearing it is part of every sane bring-up, and `clkrst.resetPeripheral` does it. pub fn setFlashbootEnabled(self: Watchdog, on: bool) void { self.config0().modify(.{wdt_flashboot_mod_en.is(@intFromBool(on))}); self.commit(); } /// Start or stop the watchdog. No `CONF_UPDATE_EN` pulse: `mwdt_ll_enable` and `_disable` /// (mwdt_ll.h:61-77) do not, so neither does this. Disabling does *not* stop flash-boot mode. pub fn setEnabled(self: Watchdog, on: bool) void { self.config0().modify(.{wdt_en.is(@intFromBool(on))}); } pub fn isEnabled(self: Watchdog) bool { return self.config0().get(wdt_en) == 1; } /// Reset the count and the stage. `TIMG_WDTFEED_REG` is a whole-word write-to-trigger register, /// so the value is irrelevant (mwdt_ll.h:219-222). pub fn feed(self: Watchdog) void { gReg(a_wdtfeed, self.group).writeRaw(1); } /// Reset the watchdog's clock divider counter. Write-to-trigger, in WDTCONFIG1 alongside the /// prescaler. pub fn resetDividerCount(self: Watchdog) void { gReg(a_wdtconfig1, self.group).modify(.{wdt_divcnt_rst.is(1)}); self.commit(); } }; /// Lift write protection and hand back the only handle that can touch the MWDT. pub fn unlock(g: Group) Watchdog { gReg(a_wdtwprotect, g).writeRaw(wkey); return .{ .group = g }; } /// Feed a watchdog, protection dance included. The one MWDT operation that is worth a shortcut, /// because it is the one called from a loop. pub fn feed(g: Group) void { const wdt = unlock(g); defer wdt.release(); wdt.feed(); } /// True if write protection is currently on, i.e. the key register holds something other than the /// key. Reads the register, so it reports the hardware rather than what this module last wrote. pub fn isWriteProtected(g: Group) bool { return gReg(a_wdtwprotect, g).raw() != wkey; } inline fn stageAction(stage: Stage) Field { // Stage 0 is at the *top* of the word (bits 30:29) and stage 3 at 24:23, i.e. the stages run // downwards through the register. The four are a uniform 2 bits apart, but in the reverse of // the obvious direction, so they are looked up rather than computed. return switch (stage) { .stage0 => Field.of(regs.TIMG_WDT_STG0_S, regs.TIMG_WDT_STG0_V), .stage1 => Field.of(regs.TIMG_WDT_STG1_S, regs.TIMG_WDT_STG1_V), .stage2 => Field.of(regs.TIMG_WDT_STG2_S, regs.TIMG_WDT_STG2_V), .stage3 => Field.of(regs.TIMG_WDT_STG3_S, regs.TIMG_WDT_STG3_V), }; } inline fn stageHoldAddr(stage: Stage) u32 { // WDTCONFIG2 holds stage 0's timeout and WDTCONFIG5 stage 3's; the mapping is off by two and // there is no macro that says so, so it comes from mwdt_ll.h:100-116. return switch (stage) { .stage0 => a_wdtconfig2, .stage1 => a_wdtconfig3, .stage2 => a_wdtconfig4, .stage3 => a_wdtconfig5, }; } test "the two strides are the documented ones" { // 0x1000 between groups (timer_group_reg.h:14, REG_TIMG_BASE) and 0x24 between the two timers // of a group. Both are asserted against the macros at comptime above; this pins the numbers so // a header change shows up as a failing test with a value in it, not only as a compile error. try std.testing.expectEqual(@as(u32, 0x1000), group_stride); try std.testing.expectEqual(@as(u32, 0x24), timer_stride); } test "the write-protect key is the register's reset value" { try std.testing.expectEqual(@as(u32, 1_356_348_065), wkey); }