use crate::pac;
const WS0_MAX_HCLK: u32 = 24_000_000;
const WS1_MAX_HCLK: u32 = 48_000_000;
const WS2_MAX_HCLK: u32 = 72_000_000;
const UNLOCK_KEY1: u32 = 0x4567_0123;
const UNLOCK_KEY2: u32 = 0xCDEF_89AB;
const BASE: u32 = 0x0800_0000;
const PAGE_SIZE: u32 = 0x400;
const PAGES_PER_WP_BIT: u32 = 4;
const WORD_SIZE: u32 = 4;
const WP_ALL_FREE: u32 = 0xFFFF_FFFF;
const OB_BASE: u32 = 0x1FFF_F800;
const BIT_NWDG_SW: u8 = 0;
const BIT_NRST_DPSLP: u8 = 1;
const BIT_NRST_STDBY: u8 = 2;
const BIT_BOOT1_N: u8 = 4;
const BIT_VDDA_VISOR: u8 = 5;
const BIT_SRAM_PARITY: u8 = 6;
fn ob_word(low: u8, high: u8) -> u32 {
let pair = |byte: u8| byte as u32 | ((!byte as u32) << 8);
pair(low) | (pair(high) << 16)
}
const fn wp_bit(page: Page) -> u32 {
let number = (page as u32 - BASE) / PAGE_SIZE;
1 << (number / PAGES_PER_WP_BIT)
}
const fn wp_protected(wp: u32, page: Page) -> bool {
wp & wp_bit(page) == 0
}
const fn user_bit(user: u8, bit: u8) -> bool {
user & (1 << bit) != 0
}
const fn set_user_bit(user: u8, bit: u8, on: bool) -> u8 {
if on {
user | (1 << bit)
} else {
user & !(1 << bit)
}
}
macro_rules! pages {
($($n:literal),* $(,)?) => {
paste::paste! {
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u32)]
pub enum Page {
$([<P $n>] = BASE + $n * PAGE_SIZE),*
}
}
};
}
#[cfg(chip_x4)]
#[rustfmt::skip]
pages!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
#[cfg(chip_x6)]
#[rustfmt::skip]
pages!(
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
);
#[cfg(chip_x8)]
#[rustfmt::skip]
pages!(
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
);
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Event {
End,
Error,
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
WriteProtected,
ProgramAlignment,
Program,
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ProtectionLevel {
None,
Low,
High,
}
impl ProtectionLevel {
const fn spc(self) -> Spc {
match self {
ProtectionLevel::None => Spc::None,
ProtectionLevel::Low => Spc::Low,
ProtectionLevel::High => Spc::High,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum Spc {
None = 0xA5,
Low = 0x00,
High = 0xCC,
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum FreeWatchdog {
Hardware,
Software,
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum LowPowerEntry {
Reset,
Enter,
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Boot1 {
Low,
High,
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum VddaMonitor {
Disabled,
Enabled,
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SramParity {
Disabled,
Enabled,
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[must_use]
pub struct OptionBytes {
protection: ProtectionLevel,
user: u8,
data: u16,
wp: u32,
}
impl Default for OptionBytes {
fn default() -> Self {
Self {
protection: ProtectionLevel::None,
user: 0xFF,
data: 0xFFFF,
wp: WP_ALL_FREE,
}
}
}
impl OptionBytes {
pub const fn no_protection(mut self) -> Self {
self.protection = ProtectionLevel::None;
self
}
pub const fn protection_low(mut self) -> Self {
self.protection = ProtectionLevel::Low;
self
}
pub const fn protection_high_forever(mut self) -> Self {
self.protection = ProtectionLevel::High;
self
}
pub const fn free_watchdog(mut self, watchdog: FreeWatchdog) -> Self {
self.user = set_user_bit(
self.user,
BIT_NWDG_SW,
matches!(watchdog, FreeWatchdog::Software),
);
self
}
pub const fn deep_sleep(mut self, entry: LowPowerEntry) -> Self {
self.user = set_user_bit(
self.user,
BIT_NRST_DPSLP,
matches!(entry, LowPowerEntry::Enter),
);
self
}
pub const fn standby(mut self, entry: LowPowerEntry) -> Self {
self.user = set_user_bit(
self.user,
BIT_NRST_STDBY,
matches!(entry, LowPowerEntry::Enter),
);
self
}
pub const fn boot1(mut self, boot1: Boot1) -> Self {
self.user = set_user_bit(self.user, BIT_BOOT1_N, matches!(boot1, Boot1::Low));
self
}
pub const fn vdda_monitor(mut self, monitor: VddaMonitor) -> Self {
self.user = set_user_bit(
self.user,
BIT_VDDA_VISOR,
matches!(monitor, VddaMonitor::Enabled),
);
self
}
pub const fn sram_parity(mut self, parity: SramParity) -> Self {
self.user = set_user_bit(
self.user,
BIT_SRAM_PARITY,
matches!(parity, SramParity::Disabled),
);
self
}
pub const fn data(mut self, data: u16) -> Self {
self.data = data;
self
}
pub const fn protect(mut self, page: Page) -> Self {
self.wp &= !wp_bit(page);
self
}
pub const fn unprotect(mut self, page: Page) -> Self {
self.wp |= wp_bit(page);
self
}
pub const fn protection(&self) -> ProtectionLevel {
self.protection
}
pub const fn get_free_watchdog(&self) -> FreeWatchdog {
if user_bit(self.user, BIT_NWDG_SW) {
FreeWatchdog::Software
} else {
FreeWatchdog::Hardware
}
}
pub const fn get_deep_sleep(&self) -> LowPowerEntry {
if user_bit(self.user, BIT_NRST_DPSLP) {
LowPowerEntry::Enter
} else {
LowPowerEntry::Reset
}
}
pub const fn get_standby(&self) -> LowPowerEntry {
if user_bit(self.user, BIT_NRST_STDBY) {
LowPowerEntry::Enter
} else {
LowPowerEntry::Reset
}
}
pub const fn get_boot1(&self) -> Boot1 {
if user_bit(self.user, BIT_BOOT1_N) {
Boot1::Low
} else {
Boot1::High
}
}
pub const fn get_vdda_monitor(&self) -> VddaMonitor {
if user_bit(self.user, BIT_VDDA_VISOR) {
VddaMonitor::Enabled
} else {
VddaMonitor::Disabled
}
}
pub const fn get_sram_parity(&self) -> SramParity {
if user_bit(self.user, BIT_SRAM_PARITY) {
SramParity::Disabled
} else {
SramParity::Enabled
}
}
pub const fn data_bytes(&self) -> u16 {
self.data
}
pub const fn is_protected(&self, page: Page) -> bool {
wp_protected(self.wp, page)
}
}
pub struct UnlockedFmc<'a> {
fmc: &'a mut Fmc,
}
impl UnlockedFmc<'_> {
fn lock(self) {
self.fmc.fmc.ctl().modify(|_, w| w.lk().lock());
}
pub fn erase_page(&mut self, page: Page) -> Result<(), Error> {
self.fmc.fmc.ctl().modify(|_, w| w.per().page_erase());
self.fmc.fmc.addr().write(|w| w.addr().bits(page as u32));
self.fmc.fmc.ctl().modify(|_, w| w.start().start());
let result = self.fmc.wait_busy();
self.fmc.fmc.ctl().modify(|_, w| w.per().clear_bit());
result
}
pub fn mass_erase(&mut self) -> Result<(), Error> {
self.fmc.fmc.ctl().modify(|_, w| w.mer().mass_erase());
self.fmc.fmc.ctl().modify(|_, w| w.start().start());
let result = self.fmc.wait_busy();
self.fmc.fmc.ctl().modify(|_, w| w.mer().clear_bit());
result
}
pub fn program(&mut self, page: Page, index: u8, word: u32) -> Result<(), Error> {
self.fmc.fmc.ctl().modify(|_, w| w.pg().program());
let addr = page as u32 + index as u32 * WORD_SIZE;
unsafe {
core::ptr::write_volatile(addr as *mut u32, word);
};
let result = self.fmc.wait_busy();
self.fmc.fmc.ctl().modify(|_, w| w.pg().clear_bit());
result
}
pub fn write_option_bytes(&mut self, ob: &OptionBytes) -> Result<(), Error> {
self.fmc.fmc.obkey().write(|w| w.obkey().bits(UNLOCK_KEY1));
self.fmc.fmc.obkey().write(|w| w.obkey().bits(UNLOCK_KEY2));
self.fmc.fmc.ctl().modify(|_, w| w.obwen().set_bit());
let result = self
.erase_option_bytes()
.and_then(|()| self.program_option_bytes(ob));
self.fmc.fmc.ctl().modify(|_, w| w.obwen().disable());
result
}
fn erase_option_bytes(&mut self) -> Result<(), Error> {
self.fmc
.fmc
.ctl()
.modify(|_, w| w.ober().option_byte_erase());
self.fmc.fmc.ctl().modify(|_, w| w.start().start());
let result = self.fmc.wait_busy();
self.fmc.fmc.ctl().modify(|_, w| w.ober().clear_bit());
result
}
fn program_option_bytes(&mut self, ob: &OptionBytes) -> Result<(), Error> {
let data = ob.data.to_le_bytes();
let wp = ob.wp.to_le_bytes();
let words = [
ob_word(ob.protection.spc() as u8, ob.user),
ob_word(data[0], data[1]),
ob_word(wp[0], wp[1]),
ob_word(wp[2], wp[3]),
];
self.fmc
.fmc
.ctl()
.modify(|_, w| w.obpg().option_byte_programming());
let mut result = Ok(());
for (index, word) in words.iter().enumerate() {
let addr = OB_BASE + index as u32 * WORD_SIZE;
unsafe { core::ptr::write_volatile(addr as *mut u32, *word) };
result = self.fmc.wait_busy();
if result.is_err() {
break;
}
}
self.fmc.fmc.ctl().modify(|_, w| w.obpg().clear_bit());
result
}
pub fn listen(&mut self, event: Event) {
self.fmc.fmc.ctl().modify(|_, w| match event {
Event::End => w.endie().enabled(),
Event::Error => w.errie().enabled(),
});
}
pub fn unlisten(&mut self, event: Event) {
self.fmc.fmc.ctl().modify(|_, w| match event {
Event::End => w.endie().disabled(),
Event::Error => w.errie().disabled(),
});
}
}
pub struct Fmc {
fmc: pac::Fmc,
}
impl Fmc {
fn wait_busy(&mut self) -> Result<(), Error> {
while self.fmc.stat().read().busy().is_active() {}
self.clear_interrupt(Event::End);
match self.take_error() {
Some(error) => Err(error),
None => Ok(()),
}
}
fn unlock(&mut self) -> UnlockedFmc<'_> {
self.fmc.key().write(|w| w.key().bits(UNLOCK_KEY1));
self.fmc.key().write(|w| w.key().bits(UNLOCK_KEY2));
UnlockedFmc { fmc: self }
}
pub fn with_unlocked<R>(&mut self, f: impl FnOnce(&mut UnlockedFmc) -> R) -> R {
let mut unlocked = self.unlock();
let result = f(&mut unlocked);
unlocked.lock();
result
}
pub fn release(self) -> pac::Fmc {
self.fmc
}
pub(crate) fn set_ws(&mut self, hclk: u32) {
self.fmc.ws().modify(|_, w| {
if hclk <= WS0_MAX_HCLK {
w.wscnt().ws0()
} else if hclk <= WS1_MAX_HCLK {
w.wscnt().ws1()
} else if hclk <= WS2_MAX_HCLK {
w.wscnt().ws2()
} else {
unreachable!()
}
});
}
pub fn is_protected(&self, page: Page) -> bool {
wp_protected(self.fmc.wp().read().bits(), page)
}
pub fn protection_level(&self) -> ProtectionLevel {
let plevel = self.fmc.obstat().read().plevel();
if plevel.is_none() {
ProtectionLevel::None
} else if plevel.is_low() {
ProtectionLevel::Low
} else {
ProtectionLevel::High
}
}
pub fn option_error(&self) -> bool {
self.fmc.obstat().read().oberr().is_error()
}
pub fn user_option(&self) -> u8 {
self.fmc.obstat().read().ob_user().bits()
}
pub fn data_option(&self) -> u16 {
self.fmc.obstat().read().ob_data().bits()
}
pub fn product_id_code(&self) -> u32 {
self.fmc.pid().read().pid().bits()
}
pub fn read_option_bytes(&self) -> OptionBytes {
OptionBytes {
protection: self.protection_level(),
user: self.user_option(),
data: self.data_option(),
wp: self.fmc.wp().read().bits(),
}
}
pub fn reload_option_bytes(&mut self) -> ! {
self.fmc.ctl().modify(|_, w| w.obrld().set_bit());
loop {
cortex_m::asm::nop();
}
}
pub fn set_prefetch(&mut self, on: bool) {
self.fmc.ws().modify(|_, w| w.pfen().bit(on));
}
pub fn is_prefetch_enabled(&self) -> bool {
self.fmc.ws().read().pfen().bit_is_set()
}
pub fn take_error(&mut self) -> Option<Error> {
let stat = self.fmc.stat().read();
if stat.wperr().is_error() {
self.fmc.stat().write(|w| w.wperr().clear());
Some(Error::WriteProtected)
} else if stat.pgaerr().bit_is_set() {
self.fmc.stat().write(|w| w.pgaerr().bit(true));
Some(Error::ProgramAlignment)
} else if stat.pgerr().is_error() {
self.fmc.stat().write(|w| w.pgerr().clear());
Some(Error::Program)
} else {
None
}
}
pub fn is_listening(&self, event: Event) -> bool {
let ctl = self.fmc.ctl().read();
match event {
Event::End => ctl.endie().is_enabled(),
Event::Error => ctl.errie().is_enabled(),
}
}
pub fn clear_interrupt(&mut self, event: Event) {
self.fmc.stat().write(|w| match event {
Event::End => w.endf().clear(),
Event::Error => w.wperr().clear().pgaerr().bit(true).pgerr().clear(),
});
}
}
pub trait FmcExt {
fn constrain(self) -> Fmc;
}
impl FmcExt for pac::Fmc {
fn constrain(self) -> Fmc {
Fmc { fmc: self }
}
}