// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The power controller of RM0433 rev 9 chapter 6, written against the `pwr` layout.
//!
//! The chapter serves the STM32H742, H743, H750 and H753. This part is the H743 in silicon revision V, so
//! where the register pages name both revisions this model follows revision V's, and the electrical
//! numbers are DS12110 rev 11 chapter 7's, which is revision V's. Every citation is RM0433 rev 9
//! unless it names another document.
//!
//! **What the model handles and what falls to the register file.** Every register of the chapter has an
//! arm in [`Device::write`], and the model overrides no read but the first after a reset. The register
//! file answers every read from the values the model keeps in it, and settles the types of the
//! hardware's own bits: `PWR_CSR1` is read-only whole, and [`WKUP_FLAGS`] restates the six wake-up
//! flags. Everything else is the model's: the write-once supply configuration, the regulator's
//! settling and the two ready flags it moves, the write protection of `PWR_CR2`, the detectors, the
//! scale the clock controller reads, and what it tells the session. The registers no firmware of
//! ArduPilot's `MatekH743` build touches, and the manual gives no behavior inside the model's reach,
//! are stored: `PWR_CPUCR`, the wake-up enables and the battery charging bits.
//!
//! **The supply configuration.** After a power-on reset the system is in Run*, section 6.4.1, with
//! the default configuration and `ACTVOSRDY` clear. The lower byte of `PWR_CR3` is "written once
//! after POR", section 6.8.4, and a firmware's first write of it starts the regulator settling, after
//! which `ACTVOSRDY` rises. The bit `SCUEN` is "single write enabled", and the model keeps the lock
//! apart from it: the first write after a power-on reset is the one, whether or not it sets `SCUEN`,
//! and a later write of the lower byte is ignored whatever `SCUEN` reads. A write of an invalid
//! combination of `LDOEN` and `BYPASS` is not written, the whole write dropped, and locks the register
//! for good: every later write is ignored and `ACTVOSRDY` stays clear. `PWR_CR3` survives a system
//! reset, section 6.8.4, and so does `PWR_CR2`, section 6.8.3, so [`Device::reset`] keeps both and the
//! first access after it puts them back, and the regulator settles again at scale 3.
//!
//! **Time.** Neither RM0433 nor DS12110 gives a time for the regulator to settle at a scale, or after
//! the supply is configured, or for the backup regulator to be ready. The model's own is 10
//! microseconds for each, long enough that a poll loop turns once and short enough to cost a firmware
//! nothing.
//!
//! | Register or section | Behavior | Source | Test |
//! |---|---|---|---|
//! | Reset state | `CR1` `0xF000 C000`, `CSR1` `0x0000 4000`, `CR3` `0x0000 0046` for revision V, `D3CR` `0x0000 4000` with `VOSRDY` read as 1 as the page says, and every other register zero | 6.8.1 to 6.8.9 | `reset_values_match_the_manual`, `vosrdy_reads_one_after_reset_and_actvosrdy_zero` |
//! | `CR3` `LDOEN`, `BYPASS`, `SCUEN` | The lower byte is written once after a power-on reset. The first write starts the regulator, and `ACTVOSRDY` rises after its settling | 6.4.1, 6.8.4, Table 34 | `the_first_supply_write_configures_the_regulator_and_actvosrdy_follows_its_settling`, `the_lower_byte_of_cr3_is_written_once` |
//! | An invalid supply combination | `LDOEN` and `BYPASS` set alike are not written, with the rest of the word, the register locks, `ACTVOSRDY` stays clear, and the firmware is told | 6.8.4, Table 34 | `an_invalid_combination_locks_the_supply_and_actvosrdy_never_rises`, `an_illegal_supply_write_is_dropped_whole_and_locks_every_later_write` |
//! | The bypass | `ACTVOSRDY` and `VOSRDY` are forced to 1, and a change of `VOS` drops neither | 6.8.2 bit 13, 6.8.6 bit 13 | `the_bypass_forces_both_ready_flags` |
//! | `D3CR` `VOS`, `VOSRDY`, `CSR1` `ACTVOS`, `ACTVOSRDY` | A new scale applies at once to `ACTVOS` and drops `ACTVOSRDY`, and `VOSRDY` too when the scale is above the settled one, since it reads 1 while the voltage is at or above the level selected. Both are up after the regulator settles. A write of the same scale changes nothing, and a change mid-settling starts its own | 6.6.2, 6.8.2, 6.8.6 | `a_new_voltage_scale_becomes_ready_after_the_regulator_settles`, `a_scale_changed_mid_settling_starts_its_own`, `writing_the_scale_it_already_has_changes_nothing` |
//! | `VOS` 00 | Reserved, selects scale 3, and is told | 6.8.6 bits 15:14 | `the_reserved_scale_encoding_selects_scale_three_and_is_told` |
//! | `VOS` before `ACTVOSRDY` | Told: "VOS shall not be changed" while the voltage levels are invalid | 6.4.1 step 5a | `a_scale_changed_before_actvosrdy_is_told` |
//! | The scale held | The model drives the scale it holds on [`SCALE_3`], [`SCALE_2`] and [`SCALE_1`], the lower of the old and the new while it settles, and raises the new request before lowering the old | 6.6.2, DS12110 rev 11 table 112 | `the_scale_requests_name_the_scale_held_with_the_new_one_raised_before_the_old_is_lowered`, `a_scale_changing_mid_settling_is_held_at_the_lower_of_the_two` |
//! | `CR2` `BREN`, `MONEN` | Write-protected until `DBP` is set, and a write made before is ignored and told | 6.8.3, 6.8.1 bit 8 | `pwr_cr2_is_write_protected_until_dbp_is_set` |
//! | `CR2` `BRRDY` | Rises after the backup regulator's settling when `BREN` is set, drops when it is cleared, and is hardware's | 6.8.3 bit 16 | `bren_raises_brrdy_and_clearing_it_drops_the_flag`, `a_backup_regulator_enable_cleared_before_it_is_ready_leaves_nothing_behind` |
//! | `CR1` `DBP` | Stored. The part's gate on `RCC_BDCR` and the RTC reads it | 6.8.1 bit 8 | `the_control_register_one_stores_dbp_and_the_stop_selection`, `the_backup_domain_refuses_a_write_until_dbp_is_set_and_answers_a_read` |
//! | `CR1` `PVDE`, `PLS`, `CSR1` `PVDO` | The output is set while `VDD` is below the level's falling threshold, held through the hysteresis until it passes the rising one, and clear while the detector is off | 6.5.3, 6.8.1, 6.8.2, DS12110 table 116 | `the_voltage_detector_reports_the_supply_against_its_threshold_with_hysteresis`, `a_disabled_detector_reports_nothing_and_so_does_the_external_level` |
//! | `CR1` `AVDEN`, `ALS`, `CSR1` `AVDO` | The same for `VDDA` and the four analog levels | 6.5.4, 6.8.1, 6.8.2, DS12110 table 116 | `the_analog_detector_reports_vdda_against_its_threshold_with_hysteresis` |
//! | `CSR1` | Read-only whole | 6.8.2 | `the_status_register_ignores_writes` |
//! | `CR3` `USB33DEN`, `USBREGEN`, `USB33RDY`, `VBE`, `VBRS` | Stored, and `USB33RDY` follows `USB33DEN` and is hardware's | 6.4.7, 6.8.4 | `the_usb_detector_and_the_battery_charging_bits_are_stored` |
//! | `CPUCR` | The deepsleep selections and `RUN_D3` are stored, `CSSF` reads zero, and the flags are hardware's | 6.8.5 | `the_cpu_control_register_stores_the_deepsleep_selection_and_cssf_reads_zero` |
//! | `WKUPCR`, `WKUPFR`, `WKUPEPR` | The enables, polarities and pulls are stored, the flags are read-only, and a written clear bit clears its flag and reads zero | 6.8.7 to 6.8.9 | `the_wakeup_registers_store_their_configuration_and_the_flags_are_read_only`, `a_wakeup_flag_clears_when_its_clear_bit_is_written` |
//! | A system reset | The supply configuration, its lock and `PWR_CR2` survive, the regulator settles at scale 3 again, and a firmware that waits for `ACTVOSRDY` gets it | 6.8.3, 6.8.4, 6.4.1 | `a_system_reset_keeps_cr2_cr3_and_the_regulator_settles_again`, `a_cr2_write_that_is_the_first_access_after_a_reset_takes`, `a_reset_through_the_runtime_keeps_the_supply_and_forgets_the_settling` |
//! | ArduPilot's start-up | The power controller's half of ChibiOS's `init_pwr` on the clock controller's bus: `ACTVOSRDY` and `VOSRDY` each after their 10 us, and no clock over scale 1 | 6.4.1, 6.8.4, 6.8.6 | `the_chibios_cold_start_reaches_pll1_at_400_mhz` in `tests/stm32h743_clock_startup.rs` |
//! | The model's state | It serializes, and a script run twice is one run | 6.8 | `the_model_state_round_trips_through_a_snapshot`, `two_runs_of_one_script_are_the_same_run` |
//!
//! **What the model leaves out, and why.**
//!
//! | Behavior | Source | Reason |
//! |---|---|---|
//! | Run* mode: writes to RAM are not permitted until `ACTVOSRDY` | 6.4.1, 6.6.1 | A memory's refusal is the engine's, not a model's |
//! | Stop, Standby, `SVOS`, `LPDS`, `FLPS`, `PDDS_Dn`, `RUN_D3`, and the flags they set, `STOPF`, `SBF`, `SBF_D1`, `SBF_D2` | 6.6.1, 6.6.3, 6.7 | The processor entering `CStop` is not an event the runtime sends a model. The selections are stored and the flags stay clear |
//! | `PLS` = 111, the external `PVD_IN` level | 6.5.3, 6.8.1 | No board drives the pin here, so `PVDO` stays clear |
//! | The detectors' wake-up and EXTI lines, `pwr_pvd_wkup` and `pwr_avd_wkup` | 6.3.1 Table 33 | The EXTI is its own instance and takes them as inputs it doesn't yet receive |
//! | The wake-up pins and the interrupt on line 149 | 6.8.7 to 6.8.9, 19.1.2 Table 147 | The pins have no source, and Table 147 lists NVIC position 149, `WKUP`, as the EXTI's `exti_wkupN_wkup` outputs and not the flags of `PWR_WKUPFR`, which the part's description lists under this instance. The registers are stored |
//! | `TEMPH`, `TEMPL`, `VBATH`, `VBATL`, `MONEN`'s monitoring | 6.5.5, 6.5.6, 6.8.3 | They compare the junction temperature and `VBAT`, which the runtime does not carry. The flags stay clear |
//! | The power-on, power-down and brownout resets, `BOR_LEV` and the supervisors' thresholds | 6.5.1, 6.5.2, DS12110 table 116 | The model has no `VDD` ramp and no power cycle. A power-on reset is a fresh part |
//! | `PWR_CR3`'s lower byte as the lane a byte or half-word store reaches | 6.8.4 | The model is handed the register's whole value with no width, so a store to the upper bytes alone is taken as a write of the lower byte |
//! | Voltage scale 0 and `SYSCFG_PWRCR.ODEN` | 6.6.2 | `SYSCFG`'s, which this model is not shown. The scales held are 3 to 1 |
//! | The USB regulator's response and the USB33 detector's | 6.4.7, 6.8.4 | The manual gives no time, so `USB33RDY` follows `USB33DEN` at once, and the board's `VDD33USB` is always there |
//! | ES0392 rev 15 2.2.23: the window watchdog is not functional when `VDD` is below 2.7 V with scale 0 or 1 | ES0392 rev 15 | Applicable to revision V (table 3: N, N, N), no workaround. It is the watchdog's, whose model does not read the rail |
//! | ES0392 rev 15 2.2.21: 480 MHz maximum CPU frequency not available | ES0392 rev 15 | The clock controller's row |
//!
//! **Where this model departs from the vendor's file.** ST's file types the six wake-up flags of
//! `PWR_WKUPFR` read-write where section 6.8.8 types them `r`, so [`WKUP_FLAGS`] restates them.
//! It gives `PWR_D3CR` the reset value `0x0000 4000` where the page adds that `VOSRDY` is read as 1
//! after a reset, so the part resets it to [`D3CR_RESET`]. Section 6.8.1 gives `PWR_CR1` the reset value `0xF000 C000`
//! though bits 31:28 are reserved, and the file keeps it, so the reserved bits read as set.
//!
//! **Determinism.** The model reads no clock and no random source. Its events are placed at absolute
//! instants from the instant of the access, and `two_runs_of_one_script_are_the_same_run` runs two
//! fresh instances through the same script and compares their registers, what they told, what they
//! drove and their whole state.
use crate::device::{Device, DeviceContext, Level, RegisterFile};
use crate::soc::stm32h743::generated::pwr::{
Register, cpucr, cr1, cr2, cr3, csr1, d3cr, wkupcr, wkupfr,
};
use crate::soc::{Access, FieldSpec, Layout, WriteModify};
use crate::time::{EventId, VirtualNanos};
/// The request this model drives when the voltage scale of scale 3 is the one it holds, on its own
/// request output zero. The clock controller reads these three to hold its clocks against the
/// datasheet's limits for the scale.
pub const SCALE_3: usize = 0;
/// The request for scale 2, output one. See [`SCALE_3`].
pub const SCALE_2: usize = 1;
/// The request for scale 1, output two. See [`SCALE_3`].
pub const SCALE_1: usize = 2;
// How long the regulator takes to settle at a new voltage scale or after the supply is configured.
// Neither RM0433 rev 9 nor DS12110 rev 11 gives a time: figure 21 and figure 31 draw the rise
// without one. This is the model's own, long enough for a poll loop to turn and short enough that a
// firmware waiting on it spends nothing.
const SETTLING: VirtualNanos = VirtualNanos(10_000);
// How long the backup regulator takes to become ready. Neither RM0433 rev 9 section 6.8.3 nor DS12110 rev 11 gives
// one, so this is the model's own, the same as [`SETTLING`].
const BACKUP_SETTLING: VirtualNanos = VirtualNanos(10_000);
const SUPPLY_ILLEGAL: &str = "RM0433 rev 9 6.8.4: LDOEN and BYPASS set alike is an invalid combination, the data is not written, the supply configuration is locked until a power-on reset, and ACTVOSRDY goes on indicating invalid voltage levels";
const VOS_BEFORE_READY: &str = "RM0433 rev 9 6.4.1 step 5a: while ACTVOSRDY indicates invalid voltage levels the system is in Run*, and VOS shall not be changed";
const VOS_RESERVED: &str = "RM0433 rev 9 6.8.6 bits 15:14: VOS 00 is reserved and selects scale 3";
const BACKUP_PROTECTED: &str =
"RM0433 rev 9 6.8.3: PWR_CR2 is write-protected until DBP is set, and the write is ignored";
// The lower byte of `PWR_CR3`, which section 6.8.4 lets be written once after a power-on reset.
const SUPPLY_BYTE: u64 = 0xFF;
// Every bit of a 32-bit register.
const FULL: u64 = 0xFFFF_FFFF;
// DS12110 rev 11 table 116 (revision V), the detector's thresholds in millivolts, typical, one
// pair per `PLS` level 0 to 6: the supply's rising edge, then its falling one. The seventh level,
// `PLS` = 111, compares the `PVD_IN` pin and has no entry.
const PVD_THRESHOLDS_MV: [(u32, u32); 7] = [
(1960, 1860),
(2100, 2010),
(2260, 2150),
(2410, 2310),
(2560, 2450),
(2710, 2610),
(2860, 2760),
];
// DS12110 rev 11 table 116 (revision V), the analog detector's thresholds in millivolts, typical,
// one pair per `ALS` level: the supply's rising edge, then its falling one.
const AVD_THRESHOLDS_MV: [(u32, u32); 4] = [(1710, 1610), (2120, 2020), (2500, 2420), (2830, 2720)];
const fn read_only(base: &FieldSpec) -> FieldSpec {
FieldSpec {
access: Access::ReadOnly,
write: WriteModify::None,
..*base
}
}
/// The six wake-up flags of `PWR_WKUPFR`, which section 6.8.8 types `r` and the vendor's file types
/// read-write. The part gives each to [`crate::soc::Builder::field`].
pub static WKUP_FLAGS: [FieldSpec; 6] = [
read_only(wkupfr::WKUPF1),
read_only(wkupfr::WKUPF2),
read_only(wkupfr::WKUPF3),
read_only(wkupfr::WKUPF4),
read_only(wkupfr::WKUPF5),
read_only(wkupfr::WKUPF6),
];
/// `PWR_D3CR` out of reset, section 6.8.6: `0x0000 4000`, "Following reset VOSRDY will be read 1 by
/// software". The vendor's file gives the first and not the parenthesis.
pub const D3CR_RESET: u64 = 0x0000_6000;
/// A voltage scale of the regulator, in order of the performance it allows.
#[derive(
Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
pub enum Scale {
/// Scale 3, the one after reset: 200 MHz at most.
Three,
/// Scale 2.
Two,
/// Scale 1.
One,
}
impl Scale {
// What `VOS` selects, section 6.8.6: 01 is scale 3, 10 scale 2 and 11 scale 1, and 00 is
// reserved and selects scale 3.
const fn from_vos(vos: u64) -> Self {
match vos {
0b10 => Self::Two,
0b11 => Self::One,
_ => Self::Three,
}
}
// The `ACTVOS` encoding of the scale.
const fn encoding(self) -> u64 {
match self {
Self::Three => 0b01,
Self::Two => 0b10,
Self::One => 0b11,
}
}
const fn line(self) -> usize {
match self {
Self::Three => SCALE_3,
Self::Two => SCALE_2,
Self::One => SCALE_1,
}
}
}
// 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.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct Shadow {
cr2: u64,
cr3: u64,
d3cr: u64,
}
impl Default for Shadow {
fn default() -> Self {
Self {
cr2: 0,
cr3: 0x0000_0046,
d3cr: D3CR_RESET,
}
}
}
// What the lower byte of `PWR_CR3` holds, which section 6.8.4 lets be written once after a power-on
// reset.
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
enum Supply {
// Not written yet: the default configuration, and `ACTVOSRDY` waiting for one.
Unwritten,
// Written with an invalid combination of `LDOEN` and `BYPASS`: locked, and `ACTVOSRDY` waiting
// forever.
Illegal,
// Written with the regulator on, which `ACTVOSRDY` and `VOSRDY` follow the settling of.
Ldo,
// Written with the regulator bypassed, which forces both ready flags to 1, sections 6.8.2 and
// 6.8.6.
Bypass,
}
impl Supply {
const fn locked(self) -> bool {
!matches!(self, Self::Unwritten)
}
const fn valid(self) -> bool {
matches!(self, Self::Ldo | Self::Bypass)
}
}
/// The power controller of one STM32H743, RM0433 rev 9 chapter 6.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Pwr {
supply_mv: u32,
analog_mv: u32,
// Whether the model has run since the part came out of reset.
started: bool,
supply: Supply,
// The regulator settling at the scale `VOS` selected, or at the supply configuration.
settling: Option<EventId>,
backup_ready: Option<EventId>,
shadow: Shadow,
// The scale `ACTVOS` says the regulator was last asked for, and the last it settled at.
applied: Scale,
settled: Scale,
// Which scale requests are high, by [`Scale::line`].
lines: [bool; 3],
}
impl Default for Pwr {
fn default() -> Self {
Self::new()
}
}
impl Pwr {
/// The power controller of a bare part, with no rail on it until a board regulates one through
/// [`Pwr::set_supply`]. A rail is a point a board picks inside the range DS12110 rev 11 table 112
/// allows the part, so the part doesn't pick one for it.
#[must_use]
pub fn new() -> Self {
Self {
supply_mv: 0,
analog_mv: 0,
started: false,
supply: Supply::Unwritten,
settling: None,
backup_ready: None,
shadow: Shadow::default(),
applied: Scale::Three,
settled: Scale::Three,
lines: [true, false, false],
}
}
/// The board's `VDD` rail has moved: an analog input of chapter 6, and what `PVDO` follows,
/// section 6.8.2 bit 4.
pub fn set_supply(&mut self, millivolts: u32, registers: &mut RegisterFile) {
self.supply_mv = millivolts;
self.refresh_detectors(registers);
}
/// The board's `VDDA` rail has moved, which `AVDO` follows, section 6.8.2 bit 16.
pub fn set_analog_supply(&mut self, millivolts: u32, registers: &mut RegisterFile) {
self.analog_mv = millivolts;
self.refresh_detectors(registers);
}
/// The `VDD` rail, in millivolts.
#[must_use]
pub const fn supply(&self) -> u32 {
self.supply_mv
}
/// The `VDDA` rail, in millivolts.
#[must_use]
pub const fn analog_supply(&self) -> u32 {
self.analog_mv
}
}
impl Pwr {
// `PVDO` and `AVDO`: set while the supply sits below the threshold the level selects, with the
// datasheet's hysteresis, and zero while the detector is off, sections 6.5.3, 6.5.4 and 6.8.2.
fn refresh_detectors(&self, registers: &mut RegisterFile) {
let control = registers.get(Register::Cr1);
let mut status = registers.get(Register::Csr1);
let pvd = usize::try_from(cr1::PLS.get(control)).expect("three bits fit a usize");
let below = |thresholds: &[(u32, u32)], level: usize, supply: u32, standing: bool| {
thresholds.get(level).is_some_and(|&(rising, falling)| {
if standing {
supply < rising
} else {
supply < falling
}
})
};
let pvdo = cr1::PVDE.is_set(control)
&& below(
&PVD_THRESHOLDS_MV,
pvd,
self.supply_mv,
csr1::PVDO.is_set(status),
);
let avd = usize::try_from(cr1::ALS.get(control)).expect("two bits fit a usize");
let avdo = cr1::AVDEN.is_set(control)
&& below(
&AVD_THRESHOLDS_MV,
avd,
self.analog_mv,
csr1::AVDO.is_set(status),
);
status = csr1::PVDO.set(status, u64::from(pvdo));
registers.set(Register::Csr1, csr1::AVDO.set(status, u64::from(avdo)));
}
// The scale the datasheet's limits are read at while the regulator settles: the lower of the one
// it was at and the one it is going to, since a rise isn't there yet and a fall is required
// before the voltage drops, section 6.8.6.
fn held_scale(&self) -> Scale {
if self.settling.is_some() {
self.settled.min(self.applied)
} else {
self.applied
}
}
// Tells the clock controller which scale is held, raising the new request before lowering the
// old so a reader never sees a moment with none.
fn drive_scale(&mut self, context: &mut DeviceContext<'_>) {
let held = self.held_scale();
for raise in [true, false] {
for scale in [Scale::Three, Scale::Two, Scale::One] {
let wanted = scale == held;
let line = scale.line();
if wanted == raise && self.lines[line] != wanted {
self.lines[line] = wanted;
context.request(line, Level::from(wanted));
}
}
}
}
// Starts the regulator settling, or does nothing at all in the bypass, where both flags are
// forced to 1.
fn settle(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
if self.supply == Supply::Bypass {
self.settling = None;
self.settled = self.applied;
Self::set_active_ready(registers, true);
Self::set_vos_ready(registers, true);
} else {
let at = VirtualNanos(context.now().0 + SETTLING.0);
self.settling = Some(context.schedule(at));
}
self.drive_scale(context);
}
// Sets `ACTVOSRDY`, section 6.8.2 bit 13.
fn set_active_ready(registers: &mut RegisterFile, ready: bool) {
let status = registers.get(Register::Csr1);
registers.set(
Register::Csr1,
csr1::ACTVOSRDY.set(status, u64::from(ready)),
);
}
// Sets `VOSRDY`, section 6.8.6 bit 13.
fn set_vos_ready(registers: &mut RegisterFile, ready: bool) {
let scale = registers.get(Register::D3cr);
registers.set(Register::D3cr, d3cr::VOSRDY.set(scale, u64::from(ready)));
}
// `USB33RDY`, section 6.8.4 bit 26: the supply is ready once the detector is enabled. The model's
// board always powers `VDD33USB` and the manual gives the detector no response time, so the flag
// follows `USB33DEN` at once.
fn follow_usb_detector(registers: &mut RegisterFile) {
let control = registers.get(Register::Cr3);
let detector = cr3::USB33DEN.get(control);
registers.set(Register::Cr3, cr3::USB33RDY.set(control, detector));
}
// `PWR_CR3`, section 6.8.4. The lower byte is written once after a power-on reset, whatever it
// holds, and the upper bytes at any time.
fn write_cr3(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
let before = self.shadow.cr3;
// 6.8.4: an invalid combination locks the configuration, and nothing written after it
// takes, the upper bytes included.
if self.supply == Supply::Illegal {
registers.restore(Register::Cr3, FULL, before);
return;
}
if self.supply.locked() {
registers.restore(Register::Cr3, SUPPLY_BYTE, before);
Self::follow_usb_detector(registers);
return;
}
let written = registers.get(Register::Cr3);
if cr3::LDOEN.is_set(written) == cr3::BYPASS.is_set(written) {
// 6.8.4: "the data is not written", all of it.
self.supply = Supply::Illegal;
registers.restore(Register::Cr3, FULL, before);
context.tell(Register::Cr3.spec().name, SUPPLY_ILLEGAL);
return;
}
Self::follow_usb_detector(registers);
self.supply = if cr3::BYPASS.is_set(written) {
Supply::Bypass
} else {
Supply::Ldo
};
self.settle(registers, context);
}
// `PWR_D3CR`, section 6.8.6: a new `VOS` is applied to the regulator, `ACTVOS` follows it, and
// both ready flags fall until the regulator has settled.
fn write_d3cr(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
let vos = d3cr::VOS.get(registers.get(Register::D3cr));
if vos == d3cr::VOS.get(self.shadow.d3cr) {
return;
}
let name = Register::D3cr.spec().name;
if !csr1::ACTVOSRDY.is_set(registers.get(Register::Csr1)) {
context.tell(name, VOS_BEFORE_READY);
}
if vos == 0 {
context.tell(name, VOS_RESERVED);
}
self.applied = Scale::from_vos(vos);
let status = registers.get(Register::Csr1);
registers.set(
Register::Csr1,
csr1::ACTVOS.set(status, self.applied.encoding()),
);
if self.supply != Supply::Bypass {
Self::set_active_ready(registers, false);
// 6.8.6 bit 13: VOSRDY reads 1 while the voltage is at or above the selected level,
// so only a rise clears it.
if self.applied > self.settled {
Self::set_vos_ready(registers, false);
}
}
self.settle(registers, context);
}
// `PWR_CR2`, section 6.8.3: write-protected until `DBP` is set, and `BREN` starts the backup
// regulator, whose `BRRDY` rises when it's ready.
fn write_cr2(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
let before = self.shadow.cr2;
if !cr1::DBP.is_set(registers.get(Register::Cr1)) {
let protected = cr2::BREN.mask() | cr2::MONEN.mask();
if (registers.get(Register::Cr2) ^ before) & protected != 0 {
registers.restore(Register::Cr2, protected, before);
context.tell(Register::Cr2.spec().name, BACKUP_PROTECTED);
}
}
let control = registers.get(Register::Cr2);
if cr2::BREN.is_set(control) {
if !cr2::BRRDY.is_set(control) && self.backup_ready.is_none() {
let at = VirtualNanos(context.now().0 + BACKUP_SETTLING.0);
self.backup_ready = Some(context.schedule(at));
}
} else {
self.backup_ready = None;
registers.set(Register::Cr2, cr2::BRRDY.set(control, 0));
}
}
fn store_shadow(&mut self, registers: &RegisterFile) {
self.shadow = Shadow {
cr2: registers.get(Register::Cr2),
cr3: registers.get(Register::Cr3),
d3cr: registers.get(Register::D3cr),
};
}
// The first call since the part came out of reset. A supply configuration that survived the
// reset, section 6.8.4: "reset only by POR", is still valid, and the regulator settles at scale
// 3 again, which is what 6.4.1 gives a firmware to wait on. Returns whether this was the first.
fn boot(
&mut self,
written: Option<Register>,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) -> bool {
if self.started {
return false;
}
self.started = true;
// 6.8.3, 6.8.4: CR2 and CR3 survive the reset, and the register file is back at its reset
// values.
// The register this call is the write of already holds the value written.
if written != Some(Register::Cr2) {
registers.set(Register::Cr2, self.shadow.cr2);
}
if written != Some(Register::Cr3) {
registers.set(Register::Cr3, self.shadow.cr3);
}
if self.supply.valid() && !csr1::ACTVOSRDY.is_set(registers.get(Register::Csr1)) {
self.settle(registers, context);
}
true
}
}
impl Device for Pwr {
type Register = Register;
/// The model answers no read. It runs for the first one after a reset, since a firmware that
/// only polls `PWR_CSR1` must still see `ACTVOSRDY` rise when the supply survived the reset.
fn read(
&mut self,
_register: Register,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) -> Option<u64> {
if self.boot(None, registers, context) {
self.store_shadow(registers);
}
None
}
fn write(
&mut self,
register: Register,
value: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
self.boot(Some(register), registers, context);
match register {
// 6.8.1: the detectors' configuration, and the backup domain's write protection `DBP`,
// which the part's gates read.
Register::Cr1 => self.refresh_detectors(registers),
// 6.8.3: the backup regulator.
Register::Cr2 => self.write_cr2(registers, context),
// 6.8.4: the supply configuration, written once.
Register::Cr3 => self.write_cr3(registers, context),
// 6.8.5: `CSSF` clears the Stop and Standby flags, which no model sets, and reads zero.
Register::Cpucr => {
if cpucr::CSSF.is_set(value) {
let control = registers.get(Register::Cpucr);
registers.set(Register::Cpucr, cpucr::CSSF.set(control, 0));
}
}
// 6.8.6: the voltage scale.
Register::D3cr => self.write_d3cr(registers, context),
// 6.8.7: a written one clears the wake-up flag, and the bits read zero.
Register::Wkupcr => {
let flags = registers.get(Register::Wkupfr);
registers.set(Register::Wkupfr, flags & !wkupcr::WKUPC.get(value));
registers.set(Register::Wkupcr, 0);
}
// 6.8.2: read-only. 6.8.8, 6.8.9: the flags are read-only, and the enables and
// polarities are stored.
Register::Csr1 | Register::Wkupfr | Register::Wkupepr => {}
}
self.store_shadow(registers);
}
fn on_event(
&mut self,
event: EventId,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
if self.settling == Some(event) {
self.settling = None;
self.settled = self.applied;
Self::set_active_ready(registers, self.supply.valid());
Self::set_vos_ready(registers, true);
self.drive_scale(context);
}
if self.backup_ready == Some(event) {
self.backup_ready = None;
let control = registers.get(Register::Cr2);
if cr2::BREN.is_set(control) {
registers.set(Register::Cr2, cr2::BRRDY.set(control, 1));
}
}
self.store_shadow(registers);
}
/// The part restarted: what a reset clears in the model goes back. The supply configuration,
/// which only a power-on reset clears, stays, and so do the rails the board holds.
fn reset(&mut self) {
*self = Self {
supply_mv: self.supply_mv,
analog_mv: self.analog_mv,
supply: self.supply,
shadow: Shadow {
cr2: self.shadow.cr2,
cr3: self.shadow.cr3,
d3cr: D3CR_RESET,
},
..Self::new()
};
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::soc::stm32h743;
#[test]
fn the_shadow_starts_at_the_reset_values() {
let mut soc = crate::mcu::Single::new(stm32h743::builder(), "PWR", Pwr::new()).unwrap();
let registers = soc.registers();
let shadow = Shadow::default();
assert_eq!(shadow.cr2, registers.get(Register::Cr2), "CR2");
assert_eq!(shadow.cr3, registers.get(Register::Cr3), "CR3");
assert_eq!(shadow.d3cr, registers.get(Register::D3cr), "D3CR");
}
}