use crate::soc::{Builder, Bus, Clock, Cpu, Gate, Peripheral, Region, WriteModify};
use super::generated;
use super::peripherals::tim::Counter;
use super::peripherals::{ADVANCED_TIMERS, CONTROLLERS, PORTS, TIMERS, adc, rcc};
pub static MEMORY: [Region; 5] = [
Region::flash("flash", 0x0800_0000, 2 << 20),
Region::ram("ccm", 0x1000_0000, 64 << 10),
Region::system("system", 0x1FFF_0000, 32 << 10),
Region::ram("sram", 0x2000_0000, 192 << 10),
Region::ram("backup_sram", 0x4002_4000, 4 << 10),
];
const INTERRUPT_LINES: u16 = 91;
const INTERRUPTS: [(&str, &[u16]); 11] = [
("ADC1", &[18]),
("ADC2", &[18]),
("TIM9", &[24]),
("TIM10", &[25]),
("TIM11", &[26]),
("TIM12", &[43]),
("TIM8", &[43, 44, 45, 46]),
("TIM6", &[54]),
("RNG", &[80]),
("SAI1", &[87]),
("DMA2D", &[90]),
];
#[cfg(test)]
const NOT_THE_VENDORS_AT_ALL: &str = "a block of the processor rather than of the part, which raises no line on the part's controller";
#[cfg(test)]
const NO_LINE_OF_ITS_OWN: [(&str, &str); 26] = [
(crate::cpu::armv7m::DWT, NOT_THE_VENDORS_AT_ALL),
("GPIOA", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
("GPIOB", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
("GPIOC", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
("GPIOD", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
("GPIOE", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
("GPIOF", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
("GPIOG", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
("GPIOH", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
("GPIOI", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
("GPIOJ", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
("GPIOK", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
("OTG_FS_HOST", ONE_PERIPHERAL_IN_SEVERAL_BLOCKS),
("OTG_FS_DEVICE", ONE_PERIPHERAL_IN_SEVERAL_BLOCKS),
("OTG_FS_PWRCLK", ONE_PERIPHERAL_IN_SEVERAL_BLOCKS),
("OTG_HS_HOST", ONE_PERIPHERAL_IN_SEVERAL_BLOCKS),
("OTG_HS_DEVICE", ONE_PERIPHERAL_IN_SEVERAL_BLOCKS),
("OTG_HS_PWRCLK", ONE_PERIPHERAL_IN_SEVERAL_BLOCKS),
("Ethernet_MMC", ONE_PERIPHERAL_IN_SEVERAL_BLOCKS),
("Ethernet_PTP", ONE_PERIPHERAL_IN_SEVERAL_BLOCKS),
("Ethernet_DMA", ONE_PERIPHERAL_IN_SEVERAL_BLOCKS),
(
"C_ADC",
"the three converters' common registers; line 18 is theirs, section 13.13.17",
),
("CRC", NOTHING_IN_TABLE_63),
("SYSCFG", NOTHING_IN_TABLE_63),
("DBG", NOTHING_IN_TABLE_63),
(
"IWDG",
"it resets the part rather than interrupting it, section 21.3",
),
];
#[cfg(test)]
const PINS_REACH_THE_CORE_THROUGH_THE_EXTI: &str = "a port's pins reach the core through the EXTI, which is its own instance and holds lines 6 to 10, \
23 and 40; section 12.2";
#[cfg(test)]
const ONE_PERIPHERAL_IN_SEVERAL_BLOCKS: &str = "ST's file splits one peripheral into several blocks and the line belongs to the whole of it, which \
is the block the file gives it to";
#[cfg(test)]
const NOTHING_IN_TABLE_63: &str = "table 63 gives it no line at all";
pub static CPU: Cpu = Cpu {
interrupt_lines: INTERRUPT_LINES,
..generated::CPU
};
const CONFIGURED: [&str; 3] = ["MODER", "OSPEEDR", "PUPDR"];
const PORT_RESETS: [(&str, [u64; CONFIGURED.len()]); PORTS.len()] = [
("GPIOA", [0xA800_0000, 0x0C00_0000, 0x6400_0000]),
("GPIOB", [0x0000_0280, 0x0000_00C0, 0x0000_0100]),
("GPIOC", [0, 0, 0]),
("GPIOD", [0, 0, 0]),
("GPIOE", [0, 0, 0]),
("GPIOF", [0, 0, 0]),
("GPIOG", [0, 0, 0]),
("GPIOH", [0, 0, 0]),
("GPIOI", [0, 0, 0]),
("GPIOJ", [0, 0, 0]),
("GPIOK", [0, 0, 0]),
];
fn instances() -> impl Iterator<Item = Peripheral> {
generated::PERIPHERALS
.iter()
.map(|&peripheral| match peripheral.name {
name if corrected(name).is_some() => Peripheral {
interrupts: corrected(name).expect("just matched"),
..peripheral
},
name if PORTS.contains(&name) => Peripheral {
registers: &generated::gpioa::REGISTERS,
..peripheral
},
name => match timer(name) {
Some(counter) => Peripheral {
registers: counter.layout(),
..peripheral
},
None => peripheral,
},
})
.chain(std::iter::once(crate::cpu::armv7m::PERIPHERAL))
}
fn corrected(name: &str) -> Option<&'static [u16]> {
INTERRUPTS
.iter()
.find(|(instance, _)| *instance == name)
.map(|(_, lines)| *lines)
}
fn timer(name: &str) -> Option<Counter> {
TIMERS
.iter()
.find(|timer| timer.instance == name)
.map(|timer| timer.counter)
}
const EVERY_TIMER: [&str; 14] = [
"TIM1", "TIM2", "TIM3", "TIM4", "TIM5", "TIM6", "TIM7", "TIM8", "TIM9", "TIM10", "TIM11",
"TIM12", "TIM13", "TIM14",
];
const SPIS: [&str; 8] = [
"SPI1", "SPI2", "SPI3", "SPI4", "SPI5", "SPI6", "I2S2ext", "I2S3ext",
];
const CLOCK_GATES: [(&str, &str, &str); 40] = [
("ADC1", "APB2ENR", "ADC1EN"),
("ADC2", "APB2ENR", "ADC2EN"),
("ADC3", "APB2ENR", "ADC3EN"),
("C_ADC", "APB2ENR", "ADC1EN"),
("USART1", "APB2ENR", "USART1EN"),
("USART2", "APB1ENR", "USART2EN"),
("USART3", "APB1ENR", "USART3EN"),
("USART6", "APB2ENR", "USART6EN"),
("UART4", "APB1ENR", "UART4EN"),
("UART5", "APB1ENR", "UART5EN"),
("UART7", "APB1ENR", "UART7ENR"),
("UART8", "APB1ENR", "UART8ENR"),
("PWR", "APB1ENR", "PWREN"),
("TIM2", "APB1ENR", "TIM2EN"),
("TIM3", "APB1ENR", "TIM3EN"),
("TIM4", "APB1ENR", "TIM4EN"),
("TIM5", "APB1ENR", "TIM5EN"),
("TIM1", "APB2ENR", "TIM1EN"),
("TIM8", "APB2ENR", "TIM8EN"),
("SPI1", "APB2ENR", "SPI1EN"),
("SPI2", "APB1ENR", "SPI2EN"),
("SPI3", "APB1ENR", "SPI3EN"),
("SPI4", "APB2ENR", "SPI4EN"),
("SPI5", "APB2ENR", "SPI5EN"),
("SPI6", "APB2ENR", "SPI6EN"),
("DMA1", "AHB1ENR", "DMA1EN"),
("DMA2", "AHB1ENR", "DMA2EN"),
("GPIOA", "AHB1ENR", "GPIOAEN"),
("GPIOB", "AHB1ENR", "GPIOBEN"),
("GPIOC", "AHB1ENR", "GPIOCEN"),
("GPIOD", "AHB1ENR", "GPIODEN"),
("GPIOE", "AHB1ENR", "GPIOEEN"),
("GPIOF", "AHB1ENR", "GPIOFEN"),
("GPIOG", "AHB1ENR", "GPIOGEN"),
("GPIOH", "AHB1ENR", "GPIOHEN"),
("GPIOI", "AHB1ENR", "GPIOIEN"),
("GPIOJ", "AHB1ENR", "GPIOJEN"),
("GPIOK", "AHB1ENR", "GPIOKEN"),
("OTG_FS_GLOBAL", "AHB2ENR", "OTGFSEN"),
("SDIO", "APB2ENR", "SDIOEN"),
];
#[cfg(test)]
const SPELLED_OTHERWISE: [(&str, &str); 4] = [
("C_ADC", "ADC1EN"),
("OTG_FS_GLOBAL", "OTGFSEN"),
("UART7", "UART7ENR"),
("UART8", "UART8ENR"),
];
pub fn builder() -> Builder {
let builder = Bus::builder(CPU, &MEMORY, instances())
.reset("RCC", "PLLI2SCFGR", 0x2400_3000)
.reset("RCC", "AHB1LPENR", 0x7EEF_97FF)
.reset("RCC", "APB1LPENR", 0xF6FE_C9FF)
.reset("RCC", "APB2LPENR", 0x0477_7F33)
.reset("DBG", "DBGMCU_IDCODE", 0x1007_0419);
let builder = super::peripherals::SERIAL_PORTS
.iter()
.fold(builder, |builder, port| {
builder.reset(port.instance, "SR", 0x0000_00C0)
});
let builder = SPIS.iter().fold(builder, |builder, instance| {
builder.reset(instance, "I2SPR", 0x0000_0002)
});
let builder = EVERY_TIMER.iter().fold(builder, |builder, timer| {
builder.write_modify(timer, "SR", WriteModify::ZeroToClear)
});
let builder = TIMERS.iter().fold(builder, |builder, timer| {
builder.reset(timer.instance, "ARR", timer.counter.mask())
});
let builder = ADVANCED_TIMERS.iter().fold(builder, |builder, timer| {
builder.reset(timer.instance, "ARR", timer.counter.mask())
});
let builder = builder.reset("OTG_FS_GLOBAL", "FS_GRSTCTL", 0x8000_0000);
let mut builder = builder;
for (port, values) in PORT_RESETS {
for (register, value) in CONFIGURED.iter().zip(values) {
builder = builder.reset(port, register, value);
}
}
for field in [&rcc::GPIOJEN, &rcc::GPIOKEN] {
builder = builder.declare("RCC", "AHB1ENR", field);
}
for (instance, register, field) in CLOCK_GATES {
builder = builder.gate(instance, Gate::by("RCC", register, field));
}
builder = builder.gate_writes("RCC", &["BDCR"], Gate::by("PWR", "CR", "DBP"));
builder = builder.gate_writes("RTC", &[], Gate::by("PWR", "CR", "DBP"));
builder = builder.reads_requests("RTC");
for instance in super::peripherals::SERIAL_INTERFACES {
builder = builder.reads_requests(instance);
}
builder = builder.clocked_by(crate::cpu::armv7m::DWT, Clock::output("RCC", rcc::HCLK));
for (instance, register, _) in CLOCK_GATES {
let serial = super::peripherals::SERIAL_INTERFACES.contains(&instance)
|| super::peripherals::SERIAL_PORTS
.iter()
.any(|port| port.instance == instance);
if !serial {
continue;
}
let output = if register == "APB1ENR" {
rcc::PCLK1
} else {
rcc::PCLK2
};
builder = builder.clocked_by(instance, Clock::output("RCC", output));
}
for timer in &TIMERS {
builder = builder.clocked_by(timer.instance, Clock::output("RCC", rcc::APB1_TIMERS));
}
for timer in &ADVANCED_TIMERS {
builder = builder.clocked_by(timer.instance, Clock::output("RCC", rcc::APB2_TIMERS));
builder = builder.reads_requests(timer.instance);
}
builder = builder.clocked_by("SDIO", Clock::output("RCC", rcc::PLL48));
builder = builder.clocked_by(
super::peripherals::ADC_COMMON,
Clock::output("RCC", rcc::PCLK2),
);
builder = builder.reads_requests(super::peripherals::ADC_COMMON);
for instance in super::peripherals::CONVERTERS {
builder = builder.clocked_by(
instance,
Clock::output(super::peripherals::ADC_COMMON, adc::ADCCLK),
);
}
builder = builder.reads_requests(super::peripherals::CONVERTERS[0]);
for controller in &CONTROLLERS {
builder = builder.reads_requests(controller.instance);
}
builder
}
#[cfg(test)]
mod tests {
use crate::soc::{Layout, RegisterSpec, Width};
use super::generated::spi1;
use super::*;
use crate::mcu::Mcu;
use crate::soc::stm32f427::peripherals::usart;
use crate::time::VirtualNanos;
#[test]
fn the_bus_carries_every_instance() {
let soc = builder().build().unwrap();
assert_eq!(
soc.peripherals().count(),
generated::PERIPHERALS.len() + 1,
"every instance of the file, and the data watchpoint unit"
);
assert!(soc.peripheral(crate::cpu::armv7m::DWT).is_some());
assert_eq!(soc.cpu().interrupt_lines, INTERRUPT_LINES);
assert!(
soc.memory()
.iter()
.any(|region| region.name == "backup_sram")
);
}
#[test]
fn every_uart_comes_out_of_reset_ready_to_be_written_to() {
use super::generated::{uart4, usart6};
let soc = builder().build().unwrap();
assert_eq!(usart6::sr::TXE.bit, uart4::sr::TXE.bit);
assert_eq!(usart6::sr::TC.bit, uart4::sr::TC.bit);
let expected = usart6::sr::TXE.mask() | usart6::sr::TC.mask();
assert_eq!(expected, 0x0000_00C0, "30.6.1: TXE is bit 7 and TC bit 6");
for port in crate::soc::stm32f427::peripherals::SERIAL_PORTS {
let instance = port.instance;
let registers = soc
.registers(instance)
.unwrap_or_else(|| panic!("{instance} is an instance"));
let value = match port.kind {
usart::Kind::Uart => registers.get(uart4::Register::Sr),
usart::Kind::Usart => registers.get(usart6::Register::Sr),
};
assert_eq!(value, expected, "30.6.1: {instance} out of reset");
}
}
#[test]
fn the_lines_the_vendor_gets_wrong_are_corrected() {
let soc = builder().build().unwrap();
for (instance, lines) in INTERRUPTS {
assert_eq!(
soc.peripheral(instance)
.map(|peripheral| peripheral.interrupts),
Some(lines),
"{instance} answers on the lines table 63 gives it"
);
}
assert!(
generated::SAI1.interrupts.is_empty() && generated::RNG.interrupts == [81],
"the generated table is the vendor's, untouched"
);
}
#[test]
fn the_generator_is_not_given_a_line_that_belongs_to_the_core() {
let soc = builder().build().unwrap();
assert_eq!(
soc.peripheral("RNG")
.map(|peripheral| peripheral.interrupts),
Some(&[80][..]),
"table 63 line 80 is `HASH_RNG` and line 81 is `FPU`"
);
assert_eq!(
soc.peripheral("HASH")
.map(|peripheral| peripheral.interrupts),
Some(&[80][..]),
"and the hash processor shares it, which is why one number serves two instances"
);
}
#[test]
fn every_instance_has_a_line_or_a_reason_for_having_none() {
let soc = builder().build().unwrap();
let unexplained: Vec<&str> = soc
.peripherals()
.filter(|peripheral| peripheral.interrupts.is_empty())
.map(|peripheral| peripheral.name)
.filter(|name| !NO_LINE_OF_ITS_OWN.iter().any(|(empty, _)| empty == name))
.collect();
assert!(
unexplained.is_empty(),
"{} instance(s) answer on no line and nothing says why; add each to NO_LINE_OF_ITS_OWN with \
its reason, or to INTERRUPTS with the line table 63 gives it: {unexplained:?}",
unexplained.len()
);
for (instance, reason) in NO_LINE_OF_ITS_OWN {
let peripheral = soc
.peripheral(instance)
.unwrap_or_else(|| panic!("{instance} is an instance"));
assert!(
peripheral.interrupts.is_empty(),
"{instance} is on the list of instances with no line and has {:?}: {reason}",
peripheral.interrupts
);
}
}
#[test]
fn a_line_is_shared_only_where_the_manual_shares_it() {
use std::collections::BTreeMap;
let soc = builder().build().unwrap();
let mut owners: BTreeMap<u16, Vec<&str>> = BTreeMap::new();
for peripheral in soc.peripherals() {
for line in peripheral.interrupts {
owners.entry(*line).or_default().push(peripheral.name);
}
}
let shared: BTreeMap<u16, Vec<&str>> = owners
.into_iter()
.filter(|(_, instances)| instances.len() > 1)
.collect();
let expected: BTreeMap<u16, Vec<&str>> = BTreeMap::from([
(18, vec!["ADC1", "ADC2", "ADC3"]),
(24, vec!["TIM1", "TIM9"]),
(25, vec!["TIM1", "TIM10"]),
(26, vec!["TIM1", "TIM11"]),
(35, vec!["I2S2ext", "SPI1", "I2S3ext"]),
(43, vec!["TIM12", "TIM8"]),
(44, vec!["TIM13", "TIM8"]),
(45, vec!["TIM8", "TIM14"]),
(54, vec!["TIM6", "DAC"]),
(80, vec!["HASH", "RNG"]),
]);
for (line, instances) in &shared {
let mut held: Vec<&str> = instances.clone();
held.sort_unstable();
let mut want = expected
.get(line)
.unwrap_or_else(|| {
panic!("line {line} is shared by {instances:?} and should not be")
})
.clone();
want.sort_unstable();
assert_eq!(held, want, "line {line}");
}
assert_eq!(
shared.len(),
expected.len(),
"every shared line, and no other"
);
}
#[test]
fn each_pair_of_timers_answers_from_the_one_layout_it_shares() {
let soc = builder().build().unwrap();
let of = |name: &str| {
soc.peripheral(name)
.unwrap_or_else(|| panic!("{name} is an instance"))
};
let (tim2, tim5) = (of("TIM2"), of("TIM5"));
let (tim3, tim4) = (of("TIM3"), of("TIM4"));
assert!(
std::ptr::eq(tim2.registers, tim5.registers),
"the two wide timers answer from one layout"
);
assert!(
std::ptr::eq(tim3.registers, tim4.registers),
"and the two narrow timers from one of their own"
);
assert!(
!std::ptr::eq(tim2.registers, tim3.registers),
"which is not the wide pair's: table 100 gives the narrow pair no option register"
);
assert_eq!(tim5.base, 0x4000_0C00);
assert_eq!(
tim5.interrupts.len(),
1,
"and it keeps its own interrupt, which is what makes it a tick rather than a counter"
);
assert_ne!(tim2.interrupts, tim5.interrupts);
}
#[test]
fn each_timer_raises_the_interrupt_of_the_instance_it_answers_for() {
use super::generated::rcc::{Register as Rcc, apb1enr};
use super::generated::tim2::{self, cr1, dier};
use crate::soc::Width;
let mut soc = crate::soc::stm32f427::Stm32f427::new().unwrap();
let at = VirtualNanos(0);
let enables = generated::RCC.base + u64::from(Rcc::Apb1enr.spec().offset);
soc.write(at, enables, Width::W32, apb1enr::TIM5EN.mask())
.expect("nothing gates the clock controller");
let register =
|which: tim2::Register| generated::TIM5.base + u64::from(which.spec().offset);
soc.write(at, register(tim2::Register::Arr), Width::W32, 9)
.expect("TIM5 answers once its clock stands");
soc.write(
at,
register(tim2::Register::Dier),
Width::W32,
dier::UIE.mask(),
)
.expect("writable");
soc.write(
at,
register(tim2::Register::Cr1),
Width::W32,
cr1::CEN.mask(),
)
.expect("writable");
soc.advance_to(VirtualNanos(1_000));
let raised = soc.drain_interrupts();
assert_eq!(raised.len(), 1, "one update, on one line: {raised:?}");
assert_eq!(raised[0].instance, "TIM5");
assert_eq!(
raised[0].interrupt, 50,
"a model raising the instance it was written against rather than the one it is answering \
for would put this on TIM2's 28"
);
}
#[test]
fn every_timer_the_part_binds_is_the_shape_it_is_bound_to() {
for timer in TIMERS {
let reference = timer.counter.layout();
let vendor = generated::PERIPHERALS
.iter()
.find(|peripheral| peripheral.name == timer.instance)
.unwrap_or_else(|| panic!("{} is an instance", timer.instance))
.registers;
assert_eq!(
vendor.len(),
reference.len(),
"{} carries {} registers where its layout carries {}",
timer.instance,
vendor.len(),
reference.len()
);
let differing: Vec<&str> = vendor
.iter()
.zip(reference.iter())
.filter(|(had, want)| {
had.offset != want.offset
|| had.fields.len() != want.fields.len()
|| had.fields.iter().zip(want.fields.iter()).any(|(one, two)| {
one.name != two.name || one.bit != two.bit || one.width != two.width
})
})
.map(|(had, _)| had.name)
.collect();
let allowed: &[&str] = match timer.counter {
Counter::Wide => &["OR"],
Counter::Narrow => &[],
};
assert!(
differing.iter().all(|register| allowed.contains(register)),
"{} differs from its layout in {differing:?}, and only {allowed:?} may",
timer.instance
);
}
}
#[test]
fn every_timer_reloads_from_all_ones_at_reset() {
let soc = builder().build().unwrap();
for timer in TIMERS {
let value = soc
.registers(timer.instance)
.unwrap_or_else(|| panic!("{} is an instance", timer.instance))
.get(generated::tim2::Register::Arr);
assert_eq!(
value,
timer.counter.mask(),
"18.4.12: {} out of reset",
timer.instance
);
}
}
#[test]
fn every_port_answers_from_the_one_layout_they_share() {
let soc = builder().build().unwrap();
let mut layouts: Vec<*const RegisterSpec> = PORTS
.iter()
.map(|port| {
soc.peripheral(port)
.unwrap_or_else(|| panic!("{port} is an instance"))
.registers
.as_ptr()
})
.collect();
layouts.sort_unstable();
layouts.dedup();
assert_eq!(
layouts.len(),
1,
"the ports answer from {} layouts",
layouts.len()
);
assert!(
!std::ptr::eq(generated::GPIOB.registers, generated::GPIOA.registers),
"the generated tables are the vendor's, untouched"
);
}
#[test]
fn every_port_comes_out_of_reset_as_the_manual_gives_it() {
use super::generated::gpioa::Register;
const NAMED_BY_THE_MANUAL: [(&str, [u64; 3]); 2] = [
("GPIOA", [0xA800_0000, 0x0C00_0000, 0x6400_0000]),
("GPIOB", [0x0000_0280, 0x0000_00C0, 0x0000_0100]),
];
let configured = [Register::Moder, Register::Ospeedr, Register::Pupdr];
let named: Vec<&str> = configured
.iter()
.map(|register| register.spec().name)
.collect();
assert_eq!(
named, CONFIGURED,
"the corrections name these three, in this order"
);
let soc = builder().build().unwrap();
for port in PORTS {
let values = NAMED_BY_THE_MANUAL
.iter()
.find(|(named, _)| *named == port)
.map_or([0, 0, 0], |(_, values)| *values);
let registers = soc
.registers(port)
.unwrap_or_else(|| panic!("{port} is an instance"));
for (register, expected) in configured.iter().zip(values) {
assert_eq!(
registers.get(*register),
expected,
"8.4: {port}.{} out of reset",
register.spec().name
);
}
}
}
#[test]
fn the_vendor_file_still_understates_port_a_output_speed() {
let vendor = generated::gpioa::REGISTERS
.iter()
.find(|spec| spec.name == "OSPEEDR")
.expect("port A carries OSPEEDR");
assert_eq!(vendor.reset, 0x0000_0000, "ST's file, untouched");
}
#[test]
fn instances_derived_from_a_sibling_share_its_table() {
let soc = builder().build().unwrap();
let (spi1, spi2) = (
soc.peripheral("SPI1").unwrap(),
soc.peripheral("SPI2").unwrap(),
);
assert!(std::ptr::eq(spi1.registers, spi2.registers));
assert_eq!(spi2.base, 0x4000_3800);
}
#[test]
fn a_register_answers_its_reset_value_once_its_clock_stands_and_not_before() {
use super::generated::rcc::{self, apb2enr};
let mut soc = crate::soc::stm32f427::Stm32f427::new().unwrap();
let status = generated::SPI1.base + u64::from(spi1::Register::Sr.spec().offset);
assert_eq!(
soc.read(VirtualNanos(0), status, Width::W32),
Err(crate::soc::Fault::Gated {
instance: "SPI1",
register: "SR",
by: Gate::by("RCC", "APB2ENR", "SPI1EN"),
}),
"2.1.11: all peripheral clocks are disabled after reset"
);
let enables = generated::RCC.base + u64::from(rcc::Register::Apb2enr.spec().offset);
soc.write(VirtualNanos(0), enables, Width::W32, apb2enr::SPI1EN.mask())
.expect("the clock controller is what holds every enable, so nothing gates it");
assert_eq!(
soc.read(VirtualNanos(0), status, Width::W32),
Ok(0x2),
"SPI_SR resets with TXE set, RM0090 28.5.3"
);
assert_eq!(spi1::cr1::SPE.bit, 6);
}
#[test]
fn every_clock_gate_is_named_for_the_instance_it_gates() {
let soc = builder().build().expect("the part assembles");
for (instance, register, field) in CLOCK_GATES {
let expected = SPELLED_OTHERWISE
.iter()
.find(|(gated, _)| *gated == instance)
.map_or_else(
|| format!("{instance}EN"),
|(_, spelling)| (*spelling).into(),
);
assert_eq!(
field, expected,
"{instance} is gated by a bit not named for it"
);
assert!(
soc.peripheral(instance).is_some(),
"{instance} is gated and is not an instance"
);
assert!(
generated::rcc::REGISTERS
.iter()
.any(|spec| spec.name == register),
"{register} is not a register of the clock controller"
);
}
}
#[test]
fn the_backup_domain_refuses_a_write_and_answers_a_read() {
use super::generated::rcc;
let mut soc = crate::soc::stm32f427::Stm32f427::new().unwrap();
let backup = generated::RCC.base + u64::from(rcc::Register::Bdcr.spec().offset);
assert_eq!(
soc.read(VirtualNanos(0), backup, Width::W32),
Ok(0),
"the register reads while the domain is shut"
);
assert!(matches!(
soc.write(VirtualNanos(0), backup, Width::W32, rcc::bdcr::LSEON.mask()),
Err(crate::soc::Fault::Gated { .. })
));
let control = generated::PWR.base + u64::from(generated::pwr::Register::Cr.spec().offset);
let enables = generated::RCC.base + u64::from(rcc::Register::Apb1enr.spec().offset);
soc.write(
VirtualNanos(0),
enables,
Width::W32,
rcc::apb1enr::PWREN.mask(),
)
.expect("the clock controller holds the regulator's enable");
soc.write(
VirtualNanos(0),
control,
Width::W32,
generated::pwr::cr::DBP.mask(),
)
.expect("the regulator answers once its clock stands");
assert!(
soc.write(VirtualNanos(0), backup, Width::W32, rcc::bdcr::LSEON.mask())
.is_ok(),
"5.5.1: DBP enables write access to these registers"
);
}
}