soc_esp32::rtc
The RTC_CNTL register block.
RTC_CNTL is a peripheral several unrelated things need: the CPU-frequency measurement wants its counter, the second core is stalled through it, the reset reason is read from it, one watchdog lives in it, and the radio asks it for the crystal frequency. Every one of those had its own copy of the base address and its own offsets, and two of them had their own copy of the latch-and-read sequence below.
Chip infrastructure that more than one driver wants is exactly what a
soc/* crate is for, so it lives here once.
It is not on the CPU clock
Section titled “It is not on the CPU clock”RTC_CNTL runs on the RTC slow clock — nominally 150 kHz from an internal RC
oscillator, against a CPU at 80 or 240 MHz. A write does not take effect
when the store retires. That is not a detail: appcpu::stall returned
immediately after requesting a stall, the caller disabled the other core’s
cache in the microseconds before the request landed, and the core executed
rubbish. Anything here that changes state has to say how the caller knows
it happened.
Register facts from esp-idf components/soc/esp32/register/soc/ rtc_cntl_reg.h, cross-checked against NuttX where the encoding is not
obvious.
Functions
Section titled “Functions”counter
Section titled “counter”unsafe fn counter(max_polls: u32) -> Option<u64>Sample the 48-bit RTC counter.
The counter is latched rather than read live: ask with TIME_UPDATE, wait
for TIME_VALID, then read the halves. Reading them without the latch can
straddle a carry and produce a time that never happened.
Returns None if the sample has not landed after max_polls reads, so a
stopped or missing RTC block fails rather than hanging the caller. Both
callers care: one runs during boot, the other inside PHY initialisation,
and a hang in either points nowhere near the clock that stopped.
esp-idf’s rtc_time_get follows this with a write to RTC_CNTL_INT_CLR_REG
to drop a stale “time valid” interrupt flag. FlintOS never enables that
interrupt, so there is nothing to clear.
Safety
Section titled “Safety”Reads and writes RTC_CNTL registers. No side effects beyond the sample request, which is what the register is for.
round_to_plausible
Section titled “round_to_plausible”fn round_to_plausible(raw_hz: u64) -> Option<u32>Snap a raw frequency measurement to the nearest plausible ESP32 CPU clock,
or None if it is more than 25% from any of them.
Rounding rather than reporting the raw number is the point: the RTC reference is an RC oscillator, so the raw figure is never exactly right, and a tick period computed from it would drift. Refusing a measurement that lands nowhere near a real clock is what keeps a stopped or misconfigured reference from being believed.
xtal_freq_mhz
Section titled “xtal_freq_mhz”unsafe fn xtal_freq_mhz() -> Option<u32>The crystal frequency in MHz as the bootloader recorded it, or None if
the register does not hold a credible one.
The encoding is not obvious and is not guessed. NuttX documents it:
Values of RTC_XTAL_FREQ_REG and RTC_APB_FREQ_REG are stored as two copies in lower and upper 16-bit halves.
and esp-idf’s rtc_clk_xtal_freq_get reads the same register at the same
offset. Two sources agreeing is what makes this safe to write.
The duplicated halves are the validity check: a register nothing has written reads as something whose halves differ. esp-idf also keeps a “ROM log disabled” flag in bit 16 — bit 0 of the upper half — and compensates by setting bit 0 of the value, so the halves still match and the low bit may be set spuriously. Hence the mask.
Returning None rather than a default is deliberate. A wrong crystal
mis-calibrates the radio, and the symptom is poor range rather than
anything pointing here, so the caller is made to decide what to do about
not knowing.
Safety
Section titled “Safety”Reads one RTC_CNTL register. No side effects.
Constants
Section titled “Constants”DEFAULT_CPU_HZ
Section titled “DEFAULT_CPU_HZ”const DEFAULT_CPU_HZ: u32;What to assume when the measurement fails, in Hz.
80 MHz, because that is what the stock esp-idf second-stage bootloader –
the one espflash writes – configures before handing off. It expects the
application to raise the clock during its own init, which FlintOS does
not do. 240 MHz is the figure an esp-idf application sees only after
calling esp_clk_init().
A caller falling back to this should say so out loud: every timeout in the system is scaled by it, and using a wrong clock silently is what caused issue #6 in the first place.
OPTIONS0
Section titled “OPTIONS0”const OPTIONS0: u32;RTC_CNTL_OPTIONS0_REG. Holds SW_STALL_APPCPU_C0 in bits [1:0].
PLAUSIBLE_CPU_HZ
Section titled “PLAUSIBLE_CPU_HZ”const PLAUSIBLE_CPU_HZ: [u32; 3];CPU frequencies the second-stage bootloader could plausibly have left the part running at.
RESET_STATE
Section titled “RESET_STATE”const RESET_STATE: u32;RTC_CNTL_RESET_STATE_REG — why each core last reset.
SLOW_HZ_NOMINAL
Section titled “SLOW_HZ_NOMINAL”const SLOW_HZ_NOMINAL: u64;Nominal RTC slow-clock rate, used as the reference when measuring the CPU frequency.
The ESP32 defaults RTC_SLOW_CLK to an internal 150 kHz RC oscillator –
untrimmed, commonly 5-10% off – unless something has switched it to the
external 32 kHz crystal or the 8 MHz/256 divider. FlintOS’s boot path does
neither. The imprecision is fine for the only question being asked: “80,
160 or 240 MHz?”, which a clock good to 10% resolves easily.
SW_CPU_STALL
Section titled “SW_CPU_STALL”const SW_CPU_STALL: u32;RTC_CNTL_SW_CPU_STALL_REG. Holds SW_STALL_APPCPU_C1 in bits [25:20].
const TIME0: u32;RTC_CNTL_TIME0_REG — the low 32 bits of the counter.
const TIME1: u32;RTC_CNTL_TIME1_REG — the high bits of the counter.
TIME_UPDATE
Section titled “TIME_UPDATE”const TIME_UPDATE: u32;RTC_CNTL_TIME_UPDATE_REG. Bit 31 requests a sample, bit 30 says it landed.
WDTCONFIG0
Section titled “WDTCONFIG0”const WDTCONFIG0: u32;RTC_CNTL_WDTCONFIG0_REG — the RTC watchdog’s control register.
WDTCONFIG1
Section titled “WDTCONFIG1”const WDTCONFIG1: u32;RTC_CNTL_WDTCONFIG1_REG — stage 0 timeout, in RTC slow-clock ticks.
WDTFEED
Section titled “WDTFEED”const WDTFEED: u32;RTC_CNTL_WDTFEED_REG.
WDTWPROTECT
Section titled “WDTWPROTECT”const WDTWPROTECT: u32;RTC_CNTL_WDTWPROTECT_REG — the write-protect key register.
XTAL_26_MHZ
Section titled “XTAL_26_MHZ”const XTAL_26_MHZ: u32;XTAL_40_MHZ
Section titled “XTAL_40_MHZ”const XTAL_40_MHZ: u32;Crystals the ESP32 supports, in MHz.
XTAL_FREQ
Section titled “XTAL_FREQ”const XTAL_FREQ: u32;RTC_XTAL_FREQ_REG, which is RTC_CNTL_STORE4_REG.
A retention register the bootloader writes, not a hardware one — nothing in silicon measures the crystal and puts it here.