// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! Reset and clock control of RM0090 rev 22 chapter 6, written against the `rcc` layout.
//!
//! Chapter 6 is the RCC of the STM32F42xxx and STM32F43xxx, which is this SoC. Chapter 7 is the same
//! peripheral for the STM32F405/407/415/417: its sections are numbered alike and read almost the same, and
//! it is the wrong part. Every citation here is chapter 6.
//!
//! | Register or section | Behaviour | Source | Test |
//! |---|---|---|---|
//! | Reset state | HSI on and ready, everything else off, HSI the system clock | 6.2.6, 6.3.1 | `reset_runs_on_the_ready_hsi` |
//! | `CR` oscillators | Each enable raises its ready flag after that oscillator's startup, and clearing the enable drops it | 6.3.1 | `an_oscillator_becomes_ready_after_its_startup`, `clearing_an_enable_drops_its_ready_flag` |
//! | `CR` `HSEBYP` | Writable only while the HSE is off | 6.3.1 | `hsebyp_is_writable_only_while_the_hse_is_off` |
//! | `CR` in-use rule | The enable of an oscillator that clocks the system, directly or through the PLL, cannot be cleared | 6.3.1, 6.2.6 | `the_running_system_clock_cannot_be_switched_off` |
//! | `CR` ready flags | Hardware's: a write cannot set one, so a firmware cannot report an oscillator locked that never started | 6.3.1 | `software_cannot_set_a_flag_hardware_owns` |
//! | `CR` startup, cancelled | An enable cleared before its startup fires leaves no stale event behind | 6.3.1 | `a_startup_cancelled_before_it_fires_leaves_nothing_behind` |
//! | `PLLCFGR` | `f(VCO) = f(input) x PLLN / PLLM`, `f(PLL) = f(VCO) / PLLP`, `f(48 MHz) = f(VCO) / PLLQ` | 6.3.2 | `the_pll_multiplies_and_divides_as_the_formula_says`, `a_factor_of_zero_yields_no_clock_rather_than_a_division_by_zero` |
//! | `PLLCFGR` while locked | A write is ignored while `PLLON` is set, and `PLLM` and `PLLSRC`, which the PLLI2S shares, while either is on | 6.2.3, 6.3.2 | `a_locked_pll_ignores_its_configuration`, `the_shared_pll_input_is_frozen_by_either_pll` |
//! | `CR` in-use rule through the PLL | Neither the PLL nor the oscillator feeding it can be stopped while the PLL clocks the system, and a PLL whose oscillator does stop is no longer ready | 6.3.1, 6.2.6 | `the_pll_and_its_source_cannot_be_stopped_while_they_clock_the_system`, `a_pll_whose_oscillator_stops_is_no_longer_ready` |
//! | `CFGR` `SW`, `SWS` | The switch happens only when the target is ready, and is deferred until it becomes ready | 6.2.6, 6.3.3 | `the_switch_waits_for_the_target_to_be_ready` |
//! | `CFGR` `HPRE` | The AHB prescaler, whose encoding has no divide by 32 | 6.3.3 | `the_ahb_prescaler_skips_thirty_two` |
//! | `CFGR` `PPRE1`, `PPRE2` | The two APB prescalers | 6.3.3 | `the_apb_prescalers_divide_the_ahb_clock` |
//! | `CFGR` `RTCPRE` | The HSE division for the RTC; 0 and 1 are no clock | 6.3.3 | `the_rtc_clock_is_the_hse_divided_or_nothing`, `rtcpre_of_zero_is_no_clock` |
//! | `CFGR` `MCO1`, `MCO2` | Both outputs, each of the four sources of either, and their prescalers | 6.3.3 | `the_clock_outputs_carry_their_source_divided`, `every_clock_output_source_is_what_the_encoding_says` |
//! | Clock outputs | Six rates the tree drives as clocks the runtime carries, numbered [`HCLK`] to [`PLL48`], each driven at the write or the ready event that changes it and at no other; the part's description says which instance runs on which, ADR-0025 | 6.2 figure 16 | `the_tree_drives_its_clocks_when_the_switch_lands`, `a_clock_is_driven_when_it_changes_and_not_otherwise`, `the_48_mhz_domain_is_nothing_until_the_pll_is_ready` |
//! | Timer clocks | `TIMPRE` clear: `PCLKx` at a prescaler of 1, else twice it. `TIMPRE` set: `HCLK` at 1, 2, or 4, else four times `PCLKx` | 6.2, 6.3.25 | `the_timer_clock_follows_the_doubling_rule`, `timpre_gives_the_timers_hclk` |
//! | `CIR` | A ready flag rises only when that source's `RDYIE` already stands, which is then what raises the interrupt, and `RDYC` clears it; each source has its own triplet | 6.3.4 | `a_ready_flag_rises_only_for_a_source_whose_interrupt_is_enabled`, `a_ready_event_sets_its_flag_and_an_enabled_one_interrupts`, `the_clear_bits_clear_and_read_zero`, `every_source_has_its_own_flag_enable_and_clear` |
//! | `CIR` flags | Bits 7:0 are hardware's; a write cannot raise one, bit 6 included, which ST's file omits | 6.3.4 | `software_cannot_set_a_flag_hardware_owns` |
//! | `BDCR` LSE | Its ready flag, and a bypass writable only while the LSE is off | 6.3.20 | `the_lse_becomes_ready_and_clocks_the_rtc`, `lsebyp_is_writable_only_while_the_lse_is_off` |
//! | `BDCR` RTC | `RTCEN` gates `RTCCLK`, and `RTCSEL` cannot be changed once chosen unless the domain is reset | 6.2.8, 6.3.20 | `the_rtc_source_is_chosen_once_and_the_enable_gates_it` |
//! | `BDCR` `BDRST` | Set and cleared by software: it stands until the firmware clears it and holds the domain at its reset value while it does | 6.3.20 | `the_backup_reset_holds_the_domain_until_it_is_cleared` |
//! | `CSR` | The LSI with its ready flag, and the reset flags, which only `RMVF` clears and a plain write leaves alone | 6.3.21 | `the_lsi_becomes_ready`, `rmvf_clears_the_reset_flags`, `a_plain_write_to_csr_keeps_the_reset_flags` |
//! | `DCKCFGR` | `TIMPRE`, and the SAI1-A source and divisors, which [`Rcc::sai_clock`] answers. `SAI1BSRC` is stored: no model consumes it | 6.3.25 | `the_sai_clock_follows_its_source_and_divisor` |
//! | `PLLI2SCFGR`, `PLLSAICFGR` | `N` on the VCO, `R` for the PLLI2S output that MCO2 and the I2S see, `Q` for the SAI; ignored while the PLL they configure is on | 6.2.3, 6.3.23, 6.3.24 | `the_audio_plls_multiply_and_divide`, `an_audio_pll_with_a_factor_of_zero_gives_no_clock`, `a_locked_audio_pll_ignores_its_configuration` |
//! | Clock security | With `CSSON` set and the HSE running, a failure disables it, sets `CSSF`, and, only where the HSE clocked the system, forces the HSI and stops the PLL and PLLI2S | 6.2.7 | `a_clock_failure_forces_the_hsi_and_flags_it`, `a_clock_failure_leaves_a_system_it_does_not_clock_alone`, `a_clock_failure_starts_the_oscillator_it_switches_to`, `a_detector_that_is_off_or_has_nothing_to_watch_reports_nothing` |
//! | `AHBxENR`, `APBxENR`, `AHBxRSTR`, `APBxRSTR` | Stored and readable, and nothing acts on them | 6.3.5 to 6.3.14 | `an_enable_bit_is_stored`, `a_reset_bit_is_stored` |
//! | `AHBxLPENR`, `APBxLPENR` | Stored; the low-power modes they qualify are not modeled, and all three registers come out of reset with every bit set, which ST's file gets wrong | 6.3.15 to 6.3.19 | `a_low_power_enable_is_stored`, `the_low_power_enables_reset_with_every_peripheral_set` |
//! | `SSCGR` | Stored. Spread spectrum modulates the PLL to spread its emissions; it has no observable effect on a clock a deterministic emulator counts in whole nanoseconds | 6.3.22 | `spread_spectrum_is_stored_and_changes_nothing` |
//! | 6.1.1, 6.1.2, 6.1.3 | The three resets, as the flags they leave in `CSR` and the domain `BDRST` clears; a software reset the part restarts from leaves `SFTRSTF` beside the flags it had | 6.1, 6.3.20, 6.3.21 | `rmvf_clears_the_reset_flags`, `the_backup_reset_holds_the_domain_until_it_is_cleared`, `a_reset_keeps_the_pins_and_the_backup_domain_and_nothing_else` |
//! | `AHB1ENR`, `APB1ENR`, `APB2ENR` | Every bit here is a gate on the instance it names: an instance whose enable is clear does not answer its registers at all. The bits are the model's and the part's binding is what consults them, ADR-0023 | 2.1.11, 6.3.10, 6.3.13, 6.3.14 | `a_register_answers_its_reset_value_once_its_clock_stands_and_not_before`, `every_clock_gate_is_named_for_the_instance_it_gates` |
//! | `BDCR` | The backup domain takes no write while `PWR_CR`'s `DBP` is clear, and answers a read throughout; the same mechanism, over one register and in one direction | 5.5.1, 6.3.20 | `the_backup_domain_refuses_a_write_and_answers_a_read` |
//!
//! **What the model leaves out, and why.**
//!
//! | Behaviour | Source | Reason |
//! |---|---|---|
//! | How long a source takes to become ready | 6.3.1, DS9405 rev 13 | Section 6.3.1 fixes none of them, so every figure is the model's own, and a ready flag is raised at the instant after its enable. The datasheet's typical times are characteristics rather than a programming contract, and modelling them would cost a firmware what it does not cost the part: ADR-0010 fixes the instruction rate whatever the clock tree says, so the 2 ms HSE is 500 000 instructions of a wait loop against the 32 000 the part retires on its 16 MHz HSI, and the 2 s LSE is half a billion. What the flag keeps is the part's order, hardware raising it rather than the write, which is what a firmware that polls can tell from one that does not. A machine that wants the datasheet's figure has no way to say so yet |
//! | A cleared enable drops its ready flag at once, where the part takes 6 oscillator cycles: 250 ns on a 24 MHz HSE, 183 us on the LSE | 6.3.1, 6.3.20 | A simplification, and the model's own. A firmware polls a ready flag to see it rise, not to watch it fall, and the direction this errs in is the one that cannot hang a wait loop |
//! | The NMI a clock failure raises | 6.2.7 | A core exception, which the interrupt bitmap cannot reach. A firmware sees `CSSF` and the forced switch |
//! | The failure event sent to the break inputs of TIM1 and TIM8 | 6.2.7 | ADR-0023 gives a part three ways to join two instances — a gate, a request line, and an access a model asks the runtime to make — and a model reading another instance's register inside its own call is none of them, and a break input is a wire rather than a register. It also puts ES0206 rev 24 2.7.1, the system break the timer does not latch, out of reach |
//! | An HSE that stops while `CSSON` is clear | 6.2.7 | The detector is what makes a failure observable; with it off, nothing in this chapter reports one |
//! | The remaining branches of figure 16: the I2S clock, whose `I2SSRC` is stored and not answered, SAI1-B, LTDC, the Ethernet and USB taps, `IWDGCLK`, and the core timer's `HCLK/8` | 6.2 | The six outputs something on the part runs on are driven as clocks; these are answered by method for a firmware that configures them and driven to nobody, because nothing consumes them. The core's timer is the core's own model |
//! | Measuring the HSI or the LSE against TIM5 or TIM11 | 6.2.11 | The wiring between a clock and a timer's input is not built |
//! | The independent watchdog forcing the LSI on, so that `LSION` cannot be cleared once it has started | 6.2.9 | The same shape as the in-use rule the model does implement for `CR`, and blocked on the same thing: `IWDG_KR` belongs to another instance, and ADR-0023 gives a part three ways to join two instances — a gate, a request line, and an access a model asks the runtime to make — and a model reading another instance's register inside its own call is none of them |
//! | Section 6.2.3's broader reading of two rules the model takes from the register sections instead | 6.2.3, 6.2.7, 6.3.2 | Chapter 6 states each twice and not alike. Section 6.2.3 says `PLLM` and `PLLSRC` are "common to both PLLs" and names all three, where section 6.3.2's own bit text says they "can be written only when the PLL and PLLI2S are disabled"; and it says the PLLs are disabled "when HSE or PLL (clocked by HSE) are used as system clock", where section 6.2.7 says "the HSE oscillator clock was the clock source of PLL used as the system clock". The model follows the register section in the first case and section 6.2.7 in the second, because each is the narrower rule and refusing a write or stopping a PLL that the part would have allowed is the failure a firmware cannot work around |
//! | Holding a peripheral in reset through `RCC_AHBxRSTR` or `RCC_APBxRSTR`, so that it answers its reset values while the bit stands | 6.3.5 to 6.3.9 | The bits are stored. Being held at reset is a third behaviour beside the gate and the request line ADR-0023 gives the part, and it is not one of them: a gated instance does not answer at all, where one held at reset answers its reset values |
//! | ES0206 rev 24 2.2.7: a peripheral does not answer for two AHB cycles, or `1 + (AHB/APB prescaler)` APB cycles, after its clock is enabled | ES0206 rev 24 | A gate opens at the instant the bit is written: ADR-0023 takes performance over cycle fidelity, and a delayed gate would mean holding an access back. Both firmwares apply the dummy-read workaround anyway |
//! | ES0206 rev 24 2.2.9: the backup domain's protection takes effect some APB1 cycles after `DBP` is programmed | ES0206 rev 24 | The same as 2.2.7 and for the same reason: the gate over `BDCR` opens at the write |
//! | ES0206 rev 24 2.2.10: PC13 transitions disturb the LSE crystal | ES0206 rev 24 | An analogue effect with no register behaviour |
//! | ES0206 rev 24 2.2.11: a backup domain reset missed after its supply drops into a window a few mV wide | ES0206 rev 24 | The model has no VBAT rail and no power cycle, so the domain always reaches its reset value |
//! | ES0206 rev 24 2.2.12: over-drive and under-drive unavailable on some revisions | ES0206 rev 24 | A power mode of `PWR`, which owns the row that settles it. What belongs here is the ceiling it would impose: the model applies no upper bound to any frequency, so a firmware may configure a tree the part could not run |
//!
//! **Where this model departs from the vendor's file.** ST's SVD splits `SW`, `SWS`, and the `PLLCFGR`
//! factors one field per bit and omits `PLLI2SQ` and `CIR`'s three `PLLSAI` bits, so the model declares
//! them as sections 6.3.2, 6.3.3, 6.3.4, and 6.3.23 give them. It types three flags read-write that those
//! sections type `r` — `CR` bit 29, `CIR` bit 6, and `CSR` bits 31:25 — and the model puts those back on
//! every write; the register file enforces the rest from the table, which stays the vendor's.
//!
use crate::device::{Device, DeviceContext, Level, RegisterFile};
use crate::soc::{Access, FieldSpec, ReadAction, WriteModify};
use crate::time::{EventId, VirtualNanos};
use crate::soc::stm32f427::generated::rcc::{
Register, bdcr, cfgr, cir, cr, csr, dckcfgr, pllcfgr, plli2scfgr, pllsaicfgr,
};
/// The RCC's one interrupt, position zero of its instance's table; RM0090 rev 22 table 63 gives it line 5.
const GLOBAL: usize = 0;
/// The internal oscillator that clocks the SoC out of reset, 16 MHz; RM0090 rev 22 section 6.2.2. With
/// `RCC_CFGR` at its reset value every bus and both timer clocks run at this, section 6.3.3, which is what
/// a model that keeps time is built with until the tree drives it otherwise.
pub const HSI_HZ: u64 = 16_000_000;
/// The clock outputs the tree drives, numbered as this model's own, RM0090 rev 22 section 6.2 figure 16:
/// the part's description says which instance runs on which, and the model drives each one whenever a
/// write or a ready event changes what it comes to.
///
/// `HCLK` is the AHB clock, "to AHB bus, core, memory and DMA", and so what the processor's own cycle
/// counter counts.
pub const HCLK: usize = 0;
/// The low-speed peripheral clock, `PCLK1`.
pub const PCLK1: usize = 1;
/// The high-speed peripheral clock, `PCLK2`.
pub const PCLK2: usize = 2;
/// What the timers on APB1 count, [`Rcc::timer_clock`] with [`Apb::One`].
pub const APB1_TIMERS: usize = 3;
/// What the timers on APB2 count, [`Rcc::timer_clock`] with [`Apb::Two`].
pub const APB2_TIMERS: usize = 4;
/// `PLL48CLK`, the 48 MHz domain the card interface, the USB core and the random number generator run
/// on, [`Rcc::pll48`].
pub const PLL48: usize = 5;
/// The clock security system's failure, driven as a pulse on this instance's request output one: the
/// break input section 17.3.12 gives the advanced-control timers, "a clock failure event, generated by the
/// Clock Security System". Output zero is the backup domain's reset, [`super::rtc::BACKUP_DOMAIN_RESET`].
pub const CLOCK_FAILURE: usize = 1;
/// How many outputs the model drives.
const OUTPUTS: usize = 6;
/// The internal low-speed oscillator. RM0090 rev 22 sections 6.2.5 and 6.3.21 disagree, saying around
/// 32 kHz and 40 kHz; DS9405 rev 13 table 42's typical is 32 kHz, which is the figure taken.
const LSI_HZ: u64 = 32_000;
const fn field(name: &'static str, bit: u8, width: u8, access: Access) -> FieldSpec {
FieldSpec {
name,
bit,
width,
access,
read: ReadAction::None,
write: WriteModify::None,
}
}
/// `SW`, the system clock switch, RM0090 rev 22 section 6.3.3; ST's file splits it into `SW0` and `SW1`.
pub static SW: FieldSpec = field("SW", 0, 2, Access::ReadWrite);
/// `SWS`, the switch status, RM0090 rev 22 section 6.3.3; read-only, and ST's file splits it.
pub static SWS: FieldSpec = field("SWS", 2, 2, Access::ReadOnly);
/// `PLLM`, RM0090 rev 22 section 6.3.2; ST's file splits it into `PLLM0` to `PLLM5`.
pub static PLLM: FieldSpec = field("PLLM", 0, 6, Access::ReadWrite);
/// `PLLN`, RM0090 rev 22 section 6.3.2; ST's file splits it into `PLLN0` to `PLLN8`.
pub static PLLN: FieldSpec = field("PLLN", 6, 9, Access::ReadWrite);
/// `PLLP`, RM0090 rev 22 section 6.3.2; ST's file splits it into `PLLP0` and `PLLP1`.
pub static PLLP: FieldSpec = field("PLLP", 16, 2, Access::ReadWrite);
/// `PLLQ`, RM0090 rev 22 section 6.3.2; ST's file splits it into `PLLQ0` to `PLLQ3`.
pub static PLLQ: FieldSpec = field("PLLQ", 24, 4, Access::ReadWrite);
/// `PLLI2SQ`, RM0090 rev 22 section 6.3.23 bits 27:24, the PLLI2S divider for the SAI1 clock; ST's file
/// omits it, where it carries `PLLSAIQ` of the sibling register.
pub static PLLI2SQ: FieldSpec = field("PLLI2SQ", 24, 4, Access::ReadWrite);
/// `PLLSAIRDYF`, RM0090 rev 22 section 6.3.4 bit 6; ST's file omits it.
pub static PLLSAIRDYF: FieldSpec = field("PLLSAIRDYF", 6, 1, Access::ReadOnly);
/// `PLLSAIRDYIE`, RM0090 rev 22 section 6.3.4 bit 14; ST's file omits it.
pub static PLLSAIRDYIE: FieldSpec = field("PLLSAIRDYIE", 14, 1, Access::ReadWrite);
/// `PLLSAIRDYC`, RM0090 rev 22 section 6.3.4 bit 22; ST's file omits it.
pub static PLLSAIRDYC: FieldSpec = field("PLLSAIRDYC", 22, 1, Access::WriteOnly);
/// Port J's clock enable, RM0090 rev 22 section 6.3.10 bit 9; ST's file declares bits 0 to 8 and stops.
pub static GPIOJEN: FieldSpec = field("GPIOJEN", 9, 1, Access::ReadWrite);
/// Port K's clock enable, RM0090 rev 22 section 6.3.10 bit 10; ST's file omits it.
pub static GPIOKEN: FieldSpec = field("GPIOKEN", 10, 1, Access::ReadWrite);
/// Port J's reset, RM0090 rev 22 section 6.3.5 bit 9; ST's file omits it.
pub static GPIOJRST: FieldSpec = field("GPIOJRST", 9, 1, Access::ReadWrite);
/// Port K's reset, RM0090 rev 22 section 6.3.5 bit 10; ST's file omits it.
pub static GPIOKRST: FieldSpec = field("GPIOKRST", 10, 1, Access::ReadWrite);
/// Port J's clock enable in sleep, RM0090 rev 22 section 6.3.15 bit 9; ST's file omits it.
pub static GPIOJLPEN: FieldSpec = field("GPIOJLPEN", 9, 1, Access::ReadWrite);
/// Port K's clock enable in sleep, RM0090 rev 22 section 6.3.15 bit 10; ST's file omits it.
pub static GPIOKLPEN: FieldSpec = field("GPIOKLPEN", 10, 1, Access::ReadWrite);
/// Every field this model declares because the vendor's file does not, or does not declare whole.
///
/// A generated table is the vendor's file transcribed, so a field ST omits or splits cannot be added to
/// one (ADR-0022); it is declared here instead. This is what lets a check ask the part what it declares
/// rather than only what ST did, and it is why none of the above needs an exemption anywhere.
pub static DECLARED: &[&FieldSpec] = &[
&SW,
&SWS,
&PLLM,
&PLLN,
&PLLP,
&PLLQ,
&PLLI2SQ,
&PLLSAIRDYF,
&PLLSAIRDYIE,
&PLLSAIRDYC,
&GPIOJEN,
&GPIOKEN,
&GPIOJRST,
&GPIOKRST,
&GPIOJLPEN,
&GPIOKLPEN,
];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Source {
Hsi,
Hse,
Pll,
PllI2s,
PllSai,
Lse,
Lsi,
}
impl Source {
/// Every source, in the order their flags sit in `CIR`; RM0090 rev 22 section 6.3.4.
const ALL: [Self; 7] = [
Self::Lsi,
Self::Lse,
Self::Hsi,
Self::Hse,
Self::Pll,
Self::PllI2s,
Self::PllSai,
];
fn control(self) -> (Register, &'static FieldSpec, &'static FieldSpec) {
match self {
Self::Hsi => (Register::Cr, cr::HSION, cr::HSIRDY),
Self::Hse => (Register::Cr, cr::HSEON, cr::HSERDY),
Self::Pll => (Register::Cr, cr::PLLON, cr::PLLRDY),
Self::PllI2s => (Register::Cr, cr::PLLI2SON, cr::PLLI2SRDY),
Self::PllSai => (Register::Cr, cr::PLLSAION, cr::PLLSAIRDY),
Self::Lse => (Register::Bdcr, bdcr::LSEON, bdcr::LSERDY),
Self::Lsi => (Register::Csr, csr::LSION, csr::LSIRDY),
}
}
fn is_pll(self) -> bool {
matches!(self, Self::Pll | Self::PllI2s | Self::PllSai)
}
/// The `CIR` flag, enable, and clear bits of this source; RM0090 rev 22 section 6.3.4.
fn interrupt(self) -> (&'static FieldSpec, &'static FieldSpec, &'static FieldSpec) {
match self {
Self::Hsi => (cir::HSIRDYF, cir::HSIRDYIE, cir::HSIRDYC),
Self::Hse => (cir::HSERDYF, cir::HSERDYIE, cir::HSERDYC),
Self::Pll => (cir::PLLRDYF, cir::PLLRDYIE, cir::PLLRDYC),
Self::PllI2s => (cir::PLLI2SRDYF, cir::PLLI2SRDYIE, cir::PLLI2SRDYC),
Self::PllSai => (&PLLSAIRDYF, &PLLSAIRDYIE, &PLLSAIRDYC),
Self::Lse => (cir::LSERDYF, cir::LSERDYIE, cir::LSERDYC),
Self::Lsi => (cir::LSIRDYF, cir::LSIRDYIE, cir::LSIRDYC),
}
}
}
/// How long a source takes to become ready. RM0090 rev 22 section 6.3.1 fixes none of them — it says
/// only that hardware sets the flag once the oscillator is stable — and DS9405 rev 13 tables 39 to 43
/// give characteristics rather than a programming contract, so any figure here is the model's own. It
/// is one microsecond for every source, which is the model's own and deliberately short: ADR-0010 fixes
/// the instruction rate whatever the clock tree says, so the datasheet's 2 ms HSE costs a wait loop
/// 500 000 instructions against the 32 000 the part retires on its 16 MHz HSI, and its 2 s LSE costs
/// half a billion. A microsecond is 250, and it is long enough that a firmware can still clear an
/// enable inside it, which is what section 6.3.1's cancelled startup and its re-arm rule are about; a
/// figure shorter than one instruction would put both out of every firmware's reach.
const STARTUP: VirtualNanos = VirtualNanos(1_000);
/// The reset and clock control of one STM32F42xxx, RM0090 rev 22 chapter 6.
#[derive(Debug)]
pub struct Rcc {
hse_hz: u64,
lse_hz: u64,
pending: [Option<EventId>; Source::ALL.len()],
shadow: Shadow,
interrupt: Level,
/// The rate last driven on each clock output, or `None` before the first, so a rate is driven when it
/// changes and not on every write that leaves it alone.
driven: [Option<u64>; OUTPUTS],
}
impl Default for Rcc {
fn default() -> Self {
Self::new()
}
}
impl Rcc {
/// The RCC of a bare part: nothing on either oscillator pin until a board fits a crystal through
/// [`Rcc::attach_crystals`]. A pin with nothing on it still reaches ready, at no frequency, which
/// RL-137 asks about.
#[must_use]
pub fn new() -> Self {
Self {
hse_hz: 0,
lse_hz: 0,
pending: [None; Source::ALL.len()],
shadow: Shadow::default(),
interrupt: Level::Low,
driven: [None; OUTPUTS],
}
}
/// The crystals a board fits, in hertz; zero for a pin it leaves empty. RM0090 rev 22 section 6.2.1
/// takes the high-speed one between 4 and 26 MHz and 6.2.4 the low-speed one at 32.768 kHz.
///
/// A crystal is soldered on, so this is for before the first instruction and takes no register file:
/// what a running oscillator does when its frequency changes underneath it is not a thing boards do and
/// not a thing this models. Whether an empty pin should reach ready at all is RL-137.
pub fn attach_crystals(&mut self, hse_hz: u64, lse_hz: u64) {
self.hse_hz = hse_hz;
self.lse_hz = lse_hz;
}
/// The crystals a board fitted, in hertz.
#[must_use]
pub const fn crystals(&self) -> (u64, u64) {
(self.hse_hz, self.lse_hz)
}
}
impl Rcc {
fn source_hz(&self, registers: &RegisterFile, source: Source) -> u64 {
let (register, _, ready) = source.control();
if !ready.is_set(registers.get(register)) {
return 0;
}
match source {
Source::Hsi => HSI_HZ,
Source::Hse => self.hse_hz,
Source::Lse => self.lse_hz,
Source::Lsi => LSI_HZ,
Source::Pll => self.pll_p(registers),
// The audio PLLs have no single output: the VCO is what their Q and R taps divide, and the
// model answers it here so that [`Rcc::plli2s_clock`] and [`Rcc::sai_clock`] can.
Source::PllI2s | Source::PllSai => self.vco(registers, source),
}
}
/// What clocks the three PLLs: `PLLSRC` picks the HSE or the HSI, RM0090 rev 22 section 6.3.2.
fn pll_input_hz(&self, registers: &RegisterFile) -> u64 {
if cr::HSERDY.is_set(registers.get(Register::Cr)) && Self::pll_source_is_hse(registers) {
self.hse_hz
} else if cr::HSIRDY.is_set(registers.get(Register::Cr))
&& !Self::pll_source_is_hse(registers)
{
HSI_HZ
} else {
0
}
}
fn pll_source_is_hse(registers: &RegisterFile) -> bool {
pllcfgr::PLLSRC.is_set(registers.get(Register::Pllcfgr))
}
/// `f(VCO) = f(input) x PLLN / PLLM`, RM0090 rev 22 section 6.3.2; the audio PLLs share `PLLM`.
fn vco(&self, registers: &RegisterFile, which: Source) -> u64 {
let pllcfgr = registers.get(Register::Pllcfgr);
let m = PLLM.get(pllcfgr);
if m == 0 {
return 0;
}
let n = match which {
Source::Pll => PLLN.get(pllcfgr),
Source::PllI2s => plli2scfgr::PLLI2SNX.get(registers.get(Register::Plli2scfgr)),
Source::PllSai => pllsaicfgr::PLLSAIN.get(registers.get(Register::Pllsaicfgr)),
_ => return 0,
};
// The manual writes the formula as f(in) x (N / M), and the division has to come second: an input
// that PLLM does not divide evenly otherwise loses up to N - 1 hertz of the VCO.
self.pll_input_hz(registers).saturating_mul(n) / m
}
/// `f(PLL) = f(VCO) / PLLP`, with `PLLP` encoding 2, 4, 6, 8; RM0090 rev 22 section 6.3.2.
fn pll_p(&self, registers: &RegisterFile) -> u64 {
let p = (PLLP.get(registers.get(Register::Pllcfgr)) + 1) * 2;
self.vco(registers, Source::Pll) / p
}
/// The 48 MHz domain: `f(VCO) / PLLQ`, RM0090 rev 22 section 6.3.2, and nothing until the PLL is
/// ready, since it is that PLL's VCO the tap divides.
#[must_use]
pub fn pll48(&self, registers: &RegisterFile) -> u64 {
let (register, _, ready) = Source::Pll.control();
let q = PLLQ.get(registers.get(Register::Pllcfgr));
if q == 0 || !ready.is_set(registers.get(register)) {
return 0;
}
self.vco(registers, Source::Pll) / q
}
/// Drives every clock output whose rate has changed, which is every entry point's last act: a write
/// to the tree and a source becoming ready are the two things that move one.
fn drive_clocks(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
let rates = [
self.hclk(registers),
self.pclk1(registers),
self.pclk2(registers),
self.timer_clock(registers, Apb::One),
self.timer_clock(registers, Apb::Two),
self.pll48(registers),
];
for (output, hz) in rates.into_iter().enumerate() {
if self.driven[output] != Some(hz) {
self.driven[output] = Some(hz);
context.clock(output, hz);
}
}
}
/// The system clock, whichever source `SWS` says is driving it; RM0090 rev 22 section 6.3.3.
#[must_use]
pub fn sysclk(&self, registers: &RegisterFile) -> u64 {
match SWS.get(registers.get(Register::Cfgr)) {
0 => self.source_hz(registers, Source::Hsi),
1 => self.source_hz(registers, Source::Hse),
2 => self.source_hz(registers, Source::Pll),
_ => 0,
}
}
/// The AHB clock, `SYSCLK / HPRE`. The encoding has no divide by 32: RM0090 rev 22 section 6.3.3.
#[must_use]
pub fn hclk(&self, registers: &RegisterFile) -> u64 {
self.sysclk(registers) / ahb_divider(cfgr::HPRE.get(registers.get(Register::Cfgr)))
}
/// The APB1 clock, `HCLK / PPRE1`; RM0090 rev 22 section 6.3.3.
#[must_use]
pub fn pclk1(&self, registers: &RegisterFile) -> u64 {
self.hclk(registers) / apb_divider(cfgr::PPRE1.get(registers.get(Register::Cfgr)))
}
/// The APB2 clock, `HCLK / PPRE2`; RM0090 rev 22 section 6.3.3.
#[must_use]
pub fn pclk2(&self, registers: &RegisterFile) -> u64 {
self.hclk(registers) / apb_divider(cfgr::PPRE2.get(registers.get(Register::Cfgr)))
}
/// What the timers on one APB domain count, RM0090 rev 22 section 6.2. That section names `TIMPRE` in
/// `RCC_CFGR` in one sentence and in `RCC_DKCFGR` in the next two; it is in `DCKCFGR`, section 6.3.25.
#[must_use]
pub fn timer_clock(&self, registers: &RegisterFile, apb: Apb) -> u64 {
let field = match apb {
Apb::One => cfgr::PPRE1,
Apb::Two => cfgr::PPRE2,
};
let divider = apb_divider(field.get(registers.get(Register::Cfgr)));
let pclk = self.hclk(registers) / divider;
if dckcfgr::TIMPRE.is_set(registers.get(Register::Dckcfgr)) {
// Section 6.2 words this as 1, 2, or 4 giving HCLK and anything else four times PCLKx. At a
// prescaler of exactly 4 the two are the same number, so only 1 and 2 tell the arms apart.
if divider <= 4 {
self.hclk(registers)
} else {
pclk.saturating_mul(4)
}
} else if divider == 1 {
pclk
} else {
pclk.saturating_mul(2)
}
}
/// The RTC clock: the LSE, the LSI, or the HSE divided by `RTCPRE`; RM0090 rev 22 sections 6.2.8, 6.3.3,
/// and 6.3.20. A `RTCPRE` of 0 or 1 is no clock, and so is an unselected source.
#[must_use]
pub fn rtc_clock(&self, registers: &RegisterFile) -> u64 {
let bdcr_value = registers.get(Register::Bdcr);
// RM0090 rev 22 section 6.3.20 bit 15: RTCEN gates RTCCLK, and figure 16 puts that gate in front
// of everything downstream of the selection.
if !bdcr::RTCEN.is_set(bdcr_value) {
return 0;
}
let select = bdcr::RTCSEL0.get(bdcr_value) | (bdcr::RTCSEL1.get(bdcr_value) << 1);
match select {
1 => self.source_hz(registers, Source::Lse),
2 => self.source_hz(registers, Source::Lsi),
3 => {
let prescaler = cfgr::RTCPRE.get(registers.get(Register::Cfgr));
if prescaler < 2 {
0
} else {
self.source_hz(registers, Source::Hse) / prescaler
}
}
_ => 0,
}
}
/// The first clock output, its source divided by its prescaler; RM0090 rev 22 section 6.3.3.
#[must_use]
pub fn mco1(&self, registers: &RegisterFile) -> u64 {
let value = registers.get(Register::Cfgr);
let source = match cfgr::MCO1.get(value) {
0 => self.source_hz(registers, Source::Hsi),
1 => self.source_hz(registers, Source::Lse),
2 => self.source_hz(registers, Source::Hse),
_ => self.source_hz(registers, Source::Pll),
};
source / mco_divider(cfgr::MCO1PRE.get(value))
}
/// The PLLI2S clock output, `f(VCO) / PLLI2SR`; RM0090 rev 22 section 6.3.23. This is what the I2S
/// peripherals and MCO2 see, where the SAI takes the Q tap instead.
#[must_use]
pub fn plli2s_clock(&self, registers: &RegisterFile) -> u64 {
let r = plli2scfgr::PLLI2SRX.get(registers.get(Register::Plli2scfgr));
if r == 0 {
return 0;
}
self.source_hz(registers, Source::PllI2s) / r
}
/// The second clock output; RM0090 rev 22 section 6.3.3.
#[must_use]
pub fn mco2(&self, registers: &RegisterFile) -> u64 {
let value = registers.get(Register::Cfgr);
let source = match cfgr::MCO2.get(value) {
0 => self.sysclk(registers),
1 => self.plli2s_clock(registers),
2 => self.source_hz(registers, Source::Hse),
_ => self.source_hz(registers, Source::Pll),
};
source / mco_divider(cfgr::MCO2PRE.get(value))
}
/// The SAI1-A clock: the PLLSAI's or the PLLI2S's Q output over its divisor, or an outside clock.
/// RM0090 rev 22 section 6.3.25. A divisor field of `n` divides by `n + 1`.
#[must_use]
pub fn sai_clock(&self, registers: &RegisterFile) -> u64 {
let dckcfgr_value = registers.get(Register::Dckcfgr);
match dckcfgr::SAI1ASRC.get(dckcfgr_value) {
0 => {
let q = pllsaicfgr::PLLSAIQ.get(registers.get(Register::Pllsaicfgr));
if q == 0 {
return 0;
}
self.source_hz(registers, Source::PllSai)
/ q
/ (dckcfgr::PLLSAIDIVQ.get(dckcfgr_value) + 1)
}
1 => {
let q = PLLI2SQ.get(registers.get(Register::Plli2scfgr));
if q == 0 {
return 0;
}
self.source_hz(registers, Source::PllI2s)
/ q
/ (dckcfgr::PLLI2SDIVQ.get(dckcfgr_value) + 1)
}
// 10 is an outside clock on the alternate function pin, which no wire reaches; 11 is what the
// manual calls a wrong configuration.
_ => 0,
}
}
}
/// Which APB domain a timer sits on.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Apb {
/// The low-speed domain, `PCLK1`.
One,
/// The high-speed domain, `PCLK2`.
Two,
}
/// `HPRE`: `0xxx` is no division, then 2, 4, 8, 16, **64**, 128, 256, 512. RM0090 rev 22 section 6.3.3
/// leaves 32 out of the encoding, so `1100` is 64 and not 32.
fn ahb_divider(encoding: u64) -> u64 {
match encoding {
0b1000 => 2,
0b1001 => 4,
0b1010 => 8,
0b1011 => 16,
0b1100 => 64,
0b1101 => 128,
0b1110 => 256,
0b1111 => 512,
_ => 1,
}
}
/// `PPRE1` and `PPRE2`: `0xx` is no division, then 2, 4, 8, 16. RM0090 rev 22 section 6.3.3.
fn apb_divider(encoding: u64) -> u64 {
match encoding {
0b100 => 2,
0b101 => 4,
0b110 => 8,
0b111 => 16,
_ => 1,
}
}
/// `MCO1PRE` and `MCO2PRE`: `0xx` is no division, then 2, 3, 4, 5. RM0090 rev 22 section 6.3.3.
fn mco_divider(encoding: u64) -> u64 {
match encoding {
0b100 => 2,
0b101 => 3,
0b110 => 4,
0b111 => 5,
_ => 1,
}
}
/// `PLLSAIRDY`, which RM0090 rev 22 section 6.3.1 types `r` and ST's file types read-write. Every other
/// ready flag of `CR`, and the HSI's factory calibration, are read-only in that file, so the register file
/// already refuses a write to them.
const CR_HARDWARE: u64 = cr::PLLSAIRDY.mask();
/// `PLLSAIRDYF`, which RM0090 rev 22 section 6.3.4 gives as bit 6 and ST's file omits, so the register's
/// own read-write access applies to it. The other six flags and `CSSF` are read-only in that file.
const CIR_HARDWARE: u64 = PLLSAIRDYF.mask();
/// The seven reset flags of `CSR` bits 31:25, `r` in RM0090 rev 22 section 6.3.21 and read-write in ST's
/// file. Only `RMVF` clears them, so without this a plain write of `LSION` would erase the record of why
/// the part restarted. `LSIRDY` beside them is read-only in that file and needs nothing.
const CSR_HARDWARE: u64 = csr::BORRSTF.mask()
| csr::PADRSTF.mask()
| csr::PORRSTF.mask()
| csr::SFTRSTF.mask()
| csr::WDGRSTF.mask()
| csr::WWDGRSTF.mask()
| csr::LPWRRSTF.mask();
/// What a write must not change, kept because a model sees the value written and not the one before it.
/// It starts at the manual's reset values, which `the_shadow_starts_at_the_reset_values` holds against the
/// register file; section 6.3.1 leaves `CR`'s `HSICAL` byte undefined and zero is the model's choice.
#[derive(Debug)]
struct Shadow {
cr: u64,
cir: u64,
csr: u64,
bdcr: u64,
pllcfgr: u64,
plli2scfgr: u64,
pllsaicfgr: u64,
}
impl Default for Shadow {
fn default() -> Self {
Self {
cr: 0x0000_0083,
cir: 0,
csr: 0x0E00_0000,
bdcr: 0,
pllcfgr: 0x2400_3010,
plli2scfgr: 0x2400_3000,
pllsaicfgr: 0x2400_3000,
}
}
}
impl Rcc {
/// Where a source's pending startup sits in the table.
fn index_of(source: Source) -> usize {
Source::ALL
.iter()
.position(|candidate| *candidate == source)
.expect("a source of the table")
}
fn requested(registers: &RegisterFile) -> Option<Source> {
match SW.get(registers.get(Register::Cfgr)) {
0 => Some(Source::Hsi),
1 => Some(Source::Hse),
2 => Some(Source::Pll),
_ => None,
}
}
fn is_ready(registers: &RegisterFile, source: Source) -> bool {
let (register, _, ready) = source.control();
ready.is_set(registers.get(register))
}
/// The switch of RM0090 rev 22 section 6.2.6: it happens only when the target is ready, and a request
/// for a source that is not yet ready is honoured the moment it becomes one.
fn try_switch(registers: &mut RegisterFile) {
let Some(target) = Self::requested(registers) else {
return;
};
if !Self::is_ready(registers, target) {
return;
}
let want = SW.get(registers.get(Register::Cfgr));
let cfgr = registers.get(Register::Cfgr);
registers.set(Register::Cfgr, SWS.set(cfgr, want));
}
/// Whether the system runs on this source, directly or through the PLL; RM0090 rev 22 section 6.3.1
/// forbids clearing the enable of one that does.
fn clocks_the_system(registers: &RegisterFile, source: Source) -> bool {
match SWS.get(registers.get(Register::Cfgr)) {
0 => source == Source::Hsi,
1 => source == Source::Hse,
2 => {
source == Source::Pll
|| (source == Source::Hse && Self::pll_source_is_hse(registers))
|| (source == Source::Hsi && !Self::pll_source_is_hse(registers))
}
_ => false,
}
}
/// Raises the RCC interrupt when any enabled ready flag stands; RM0090 rev 22 section 6.3.4.
///
/// The model keeps the level it last drove, so a write that changes nothing reports nothing.
fn refresh_interrupt(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
let cir_value = registers.get(Register::Cir);
let wanted = if Source::ALL.iter().any(|source| {
let (flag, enable, _) = source.interrupt();
flag.is_set(cir_value) && enable.is_set(cir_value)
}) {
Level::High
} else {
Level::Low
};
if wanted == self.interrupt {
return;
}
self.interrupt = wanted;
context.interrupt(GLOBAL, wanted);
}
/// The HSE has stopped and the clock security system notices, RM0090 rev 22 section 6.2.7. Every
/// clause of that section is conditional on what the oscillator was clocking, and the model's two
/// tables say which of them it carries.
pub fn fail_hse(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
let cr_before = registers.get(Register::Cr);
if !cr::CSSON.is_set(cr_before) || !cr::HSERDY.is_set(cr_before) {
return;
}
let feeds_the_system = Self::clocks_the_system(registers, Source::Hse);
let through_the_pll = feeds_the_system && SWS.get(registers.get(Register::Cfgr)) == 2;
let mut cr_value = cr::HSEON.set(cr_before, 0);
cr_value = cr::HSERDY.set(cr_value, 0);
self.pending[Self::index_of(Source::Hse)] = None;
if through_the_pll {
for source in [Source::Pll, Source::PllI2s] {
self.pending[Self::index_of(source)] = None;
}
cr_value = cr::PLLON.set(cr_value, 0);
cr_value = cr::PLLRDY.set(cr_value, 0);
cr_value = cr::PLLI2SON.set(cr_value, 0);
cr_value = cr::PLLI2SRDY.set(cr_value, 0);
}
if feeds_the_system {
// Hardware starts the oscillator it switches to; a firmware is free to have left it off.
cr_value = cr::HSION.set(cr_value, 1);
cr_value = cr::HSIRDY.set(cr_value, 1);
}
registers.set(Register::Cr, cr_value);
if feeds_the_system {
let mut cfgr_value = SW.set(registers.get(Register::Cfgr), 0);
cfgr_value = SWS.set(cfgr_value, 0);
registers.set(Register::Cfgr, cfgr_value);
}
let cir_value = registers.get(Register::Cir);
registers.set(Register::Cir, cir::CSSF.set(cir_value, 1));
context.request(CLOCK_FAILURE, Level::High);
context.request(CLOCK_FAILURE, Level::Low);
self.store_shadow(registers);
self.refresh_interrupt(registers, context);
self.drive_clocks(registers, context);
}
}
impl Device for Rcc {
type Register = Register;
fn write(
&mut self,
register: Register,
value: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
match register {
Register::Cr => self.write_cr(registers, context),
Register::Cfgr => {
Self::try_switch(registers);
}
Register::Cir => self.write_cir(value, registers, context),
Register::Bdcr => self.write_bdcr(registers, context),
Register::Csr => self.write_csr(value, registers, context),
Register::Pllcfgr | Register::Plli2scfgr | Register::Pllsaicfgr => {
self.guard_pll_configuration(register, registers);
}
_ => {}
}
self.store_shadow(registers);
self.drive_clocks(registers, context);
}
fn on_event(
&mut self,
event: EventId,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let Some(index) = self
.pending
.iter()
.position(|pending| *pending == Some(event))
else {
return;
};
self.pending[index] = None;
let source = Source::ALL[index];
let (register, _, ready) = source.control();
let value = registers.get(register);
registers.set(register, ready.set(value, 1));
// RM0090 rev 22 section 6.3.4: each flag "is set by hardware when ... and xRDYIE is set". A firmware
// that polls a ready flag with its interrupt disabled is polling a bit hardware never raises; the
// ready flag of section 6.3.1 is the one to poll, and that is the one the line above sets.
let (flag, enable, _) = source.interrupt();
let cir_value = registers.get(Register::Cir);
if enable.is_set(cir_value) {
registers.set(Register::Cir, flag.set(cir_value, 1));
}
self.follow_cr_enables(registers, context);
Self::try_switch(registers);
self.store_shadow(registers);
self.refresh_interrupt(registers, context);
self.drive_clocks(registers, context);
}
}
impl Rcc {
/// The part has restarted from a software reset, RM0090 rev 22 sections 6.1.1 and 6.3.21: `SFTRSTF`
/// joins the reset flags it had, which only `RMVF` clears.
pub fn restarted_by_software(&mut self, flags_before: u64, registers: &mut RegisterFile) {
registers.restore(
Register::Csr,
CSR_HARDWARE,
flags_before | csr::SFTRSTF.mask(),
);
self.store_shadow(registers);
}
fn store_shadow(&mut self, registers: &RegisterFile) {
self.shadow = Shadow {
cr: registers.get(Register::Cr),
cir: registers.get(Register::Cir),
csr: registers.get(Register::Csr),
bdcr: registers.get(Register::Bdcr),
pllcfgr: registers.get(Register::Pllcfgr),
plli2scfgr: registers.get(Register::Plli2scfgr),
pllsaicfgr: registers.get(Register::Pllsaicfgr),
};
}
/// `CR`: the five enables and their ready flags, the bypass rule, and the in-use rule.
fn write_cr(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
registers.restore(Register::Cr, CR_HARDWARE, self.shadow.cr);
// RM0090 rev 22 section 6.3.1: HSEBYP is writable only while the HSE oscillator is disabled.
if cr::HSEON.is_set(self.shadow.cr) {
registers.restore(Register::Cr, cr::HSEBYP.mask(), self.shadow.cr);
}
self.follow_cr_enables(registers, context);
self.refresh_interrupt(registers, context);
}
/// Arms, holds, or drops the startup of every source whose enable lives in `CR`, RM0090 rev 22
/// section 6.3.1. A ready event runs it as well as a write, because an oscillator becoming ready is
/// what lets a PLL already enabled against it start locking.
fn follow_cr_enables(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
for (index, source) in Source::ALL.iter().enumerate() {
let (register, enable, ready) = source.control();
if register != Register::Cr {
continue;
}
let value = registers.get(register);
if !enable.is_set(value) {
if Self::clocks_the_system(registers, *source) {
registers.set(register, enable.set(value, 1));
} else {
registers.set(register, ready.set(value, 0));
self.pending[index] = None;
}
continue;
}
// Section 6.3.1 forbids stopping an oscillator that feeds a PLL clocking the system, and says
// nothing about one that stops while the system runs elsewhere. Holding the flag there would
// let section 6.2.6's switch accept a PLL at no frequency, so the model drops it.
if source.is_pll() && self.pll_input_hz(registers) == 0 {
registers.set(register, ready.set(value, 0));
self.pending[index] = None;
continue;
}
if !ready.is_set(value) && self.pending[index].is_none() {
self.pending[index] =
Some(context.schedule(VirtualNanos(context.now().0 + STARTUP.0)));
}
}
}
/// `CIR`: the clear bits clear their flags and read zero; RM0090 rev 22 section 6.3.4.
fn write_cir(
&mut self,
written: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
registers.restore(Register::Cir, CIR_HARDWARE, self.shadow.cir);
let mut value = registers.get(Register::Cir);
for source in Source::ALL {
let (flag, _, clear) = source.interrupt();
if clear.is_set(written) {
value = flag.set(value, 0);
}
value = clear.set(value, 0);
}
if cir::CSSC.is_set(written) {
value = cir::CSSF.set(value, 0);
}
value = cir::CSSC.set(value, 0);
registers.set(Register::Cir, value);
self.refresh_interrupt(registers, context);
}
/// `BDCR`: the LSE, the RTC source, and the backup reset; RM0090 rev 22 section 6.3.20.
fn write_bdcr(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
// RM0090 rev 22 section 6.3.20: BDRST is set and cleared by software, so it stands until the
// firmware clears it and holds the rest of the domain at its reset value while it does.
if bdcr::BDRST.is_set(registers.get(Register::Bdcr)) {
registers.set(Register::Bdcr, bdcr::BDRST.mask());
self.pending[Self::index_of(Source::Lse)] = None;
// The rest of the domain is the RTC's, and a reset is a line rather than a reach into
// another model's registers; ADR-0023. Held high for as long as the firmware holds the bit.
context.request(super::rtc::BACKUP_DOMAIN_RESET, Level::High);
self.refresh_interrupt(registers, context);
return;
}
context.request(super::rtc::BACKUP_DOMAIN_RESET, Level::Low);
// RM0090 rev 22 section 6.3.20 bit 2: LSEBYP is writable only while the LSE clock is disabled,
// which is the rule section 6.3.1 gives HSEBYP.
if bdcr::LSEON.is_set(self.shadow.bdcr) {
registers.restore(Register::Bdcr, bdcr::LSEBYP.mask(), self.shadow.bdcr);
}
// RM0090 rev 22 section 6.3.20 bits 9:8 and section 6.2.8: once the RTC source has been chosen it
// cannot be changed again unless the backup domain is reset, and BDRST above is what resets it.
let selection = bdcr::RTCSEL0.mask() | bdcr::RTCSEL1.mask();
if self.shadow.bdcr & selection != 0 {
registers.restore(Register::Bdcr, selection, self.shadow.bdcr);
}
self.follow_enable(Source::Lse, registers, context);
}
/// `CSR`: the LSI, and `RMVF` clearing the reset flags; RM0090 rev 22 section 6.3.21.
fn write_csr(
&mut self,
written: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
registers.restore(Register::Csr, CSR_HARDWARE, self.shadow.csr);
if csr::RMVF.is_set(written) {
let mut value = registers.get(Register::Csr);
for flag in [
csr::BORRSTF,
csr::PADRSTF,
csr::PORRSTF,
csr::SFTRSTF,
csr::WDGRSTF,
csr::WWDGRSTF,
csr::LPWRRSTF,
] {
value = flag.set(value, 0);
}
registers.set(Register::Csr, csr::RMVF.set(value, 0));
}
self.follow_enable(Source::Lsi, registers, context);
}
/// One oscillator outside `CR`: schedule its ready when enabled, drop it when not.
fn follow_enable(
&mut self,
source: Source,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let index = Self::index_of(source);
let (register, enable, ready) = source.control();
let value = registers.get(register);
if enable.is_set(value) {
if !ready.is_set(value) && self.pending[index].is_none() {
self.pending[index] =
Some(context.schedule(VirtualNanos(context.now().0 + STARTUP.0)));
}
} else {
registers.set(register, ready.set(value, 0));
self.pending[index] = None;
}
self.refresh_interrupt(registers, context);
}
/// RM0090 rev 22 section 6.2.3: a PLL's factors cannot change while it is enabled, so the write is
/// ignored and the register keeps what it had.
fn guard_pll_configuration(&mut self, register: Register, registers: &mut RegisterFile) {
let (locked, previous) = match register {
Register::Pllcfgr => (cr::PLLON, self.shadow.pllcfgr),
Register::Plli2scfgr => (cr::PLLI2SON, self.shadow.plli2scfgr),
Register::Pllsaicfgr => (cr::PLLSAION, self.shadow.pllsaicfgr),
_ => return,
};
let cr_value = registers.get(Register::Cr);
if locked.is_set(cr_value) {
registers.set(register, previous);
return;
}
// RM0090 rev 22 section 6.3.2 bits 5:0 and 22: PLLM and PLLSRC feed the PLLI2S too, so each can be
// written only while both are disabled. The rest of PLLCFGR is the PLL's, and the guard above has it.
// A firmware that writes the whole word while the PLLI2S runs therefore gets the factors it asked
// for and the input it had, which is what a per-field interlock does on the part: the bits that
// may not be written simply do not latch, and the ones beside them do.
if register == Register::Pllcfgr && cr::PLLI2SON.is_set(cr_value) {
let value = registers.get(register);
let kept = PLLM.set(value, PLLM.get(previous));
registers.set(
register,
pllcfgr::PLLSRC.set(kept, pllcfgr::PLLSRC.get(previous)),
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::soc::stm32f427;
#[test]
fn the_shadow_starts_at_the_reset_values() {
let mut soc = crate::mcu::Single::new(stm32f427::builder(), "RCC", Rcc::new()).unwrap();
let registers = soc.registers();
let shadow = Shadow::default();
for (name, seeded, register) in [
("CR", shadow.cr, Register::Cr),
("CIR", shadow.cir, Register::Cir),
("CSR", shadow.csr, Register::Csr),
("BDCR", shadow.bdcr, Register::Bdcr),
("PLLCFGR", shadow.pllcfgr, Register::Pllcfgr),
("PLLI2SCFGR", shadow.plli2scfgr, Register::Plli2scfgr),
("PLLSAICFGR", shadow.pllsaicfgr, Register::Pllsaicfgr),
] {
assert_eq!(seeded, registers.get(register), "{name} out of reset");
}
}
}