atsamd_hal/peripherals/nvm/
mod.rs

1//! # Non-volatile Memory Controller
2//!
3//! This module allows users to interact with non-volatile memory controller.
4//!
5//! Nvmctrl is an intermediary between memory buses and physical non-volatile
6//! memory. It provides means of managing a flash memory content, its properties
7//! (cache, wait states, bootloader blocks protection), power management and
8//! address remapping if necessary (in case bank mechanism is used). It also
9//! provides an indirection mechanism to achieve non-volatile RAM-like memory
10//! within last sectors of a physical flash (More in [`smart_eeprom`] module).
11//!
12//! NVM supports splitting flash into two sections (opt-in feature) called
13//! banks. Bank considered active is mapped to _virtual_ address `0x0`, meaning
14//! it contains currently executed application. Through NVM command & control
15//! interface, banks can be swapped and MCU reset, so the firmware from the
16//! other bank will run after restart.
17//!
18//! Module features:
19//! - Erase & write over non-volatile memory in a device.
20//! - Swap banks
21#![warn(missing_docs)]
22
23pub mod smart_eeprom;
24
25pub use crate::pac::nvmctrl::ctrla::Prmselect;
26use crate::pac::nvmctrl::ctrlb::Cmdselect;
27use crate::pac::Nvmctrl;
28use core::num::NonZeroU32;
29use core::ops::Range;
30use core::ptr::addr_of;
31
32use bitfield::bitfield;
33
34/// Retrieve a total NVM size using HW registers
35#[inline(always)]
36pub fn retrieve_flash_size() -> u32 {
37    static mut FLASHSIZE: Option<NonZeroU32> = None;
38    // Safety: Lazy initialization of a static variable - interactions with
39    // `Option<NonZeroU32>` should be atomic
40    unsafe {
41        match FLASHSIZE {
42            Some(x) => x.into(),
43            None => {
44                let nvm = &*Nvmctrl::ptr();
45                let nvm_params = nvm.param().read();
46                if !nvm_params.psz().is_512() {
47                    unreachable!("NVM page size is always expected to be 512 bytes");
48                }
49                let nvm_pages = nvm_params.nvmp().bits() as u32;
50                let flash_size = nvm_pages * 512;
51                // Safety: `flash_size` will never be 0
52                FLASHSIZE = Some(NonZeroU32::new_unchecked(flash_size));
53                flash_size
54            }
55        }
56    }
57}
58
59/// Retrieve a bank size using HW registers
60#[inline(always)]
61pub fn retrieve_bank_size() -> u32 {
62    retrieve_flash_size() / 2
63}
64
65/// Size of a page in bytes
66pub const PAGESIZE: u32 = 512;
67
68/// Size of one block
69pub const BLOCKSIZE: u32 = 512 * 16;
70
71/// Size of a quad word
72pub const QUADWORDSIZE: u32 = 16;
73
74/// Non-volatile memory controller
75pub struct Nvm {
76    /// PAC peripheral
77    nvm: Nvmctrl,
78}
79
80/// Errors generated by the NVM peripheral
81#[derive(Debug, Clone, Copy, Eq, PartialEq)]
82#[cfg_attr(feature = "defmt", derive(defmt::Format))]
83pub enum PeripheralError {
84    /// NVM error
85    NvmError,
86    /// Single ECC error
87    EccSingleError,
88    /// Dual ECC error
89    EccDualError,
90    /// Locked error
91    LockError,
92    /// Programming error
93    ProgrammingError,
94    /// Address error
95    AddressError,
96}
97
98/// Driver errors
99#[non_exhaustive]
100#[derive(Debug, Clone, Copy, Eq, PartialEq)]
101#[cfg_attr(feature = "defmt", derive(defmt::Format))]
102pub enum Error {
103    /// Address range outside of flash
104    NonFlash,
105    /// Target sector is protected
106    Protected,
107    /// Memory region is used by SmartEEPROM
108    SmartEepromArea,
109    /// Errors generated by hardware
110    Peripheral(PeripheralError),
111    /// The DSU failed in some way
112    Dsu(super::dsu::Error),
113    /// An alignment requirement was not fulfilled
114    Alignment,
115}
116
117/// Physical flash banks
118#[derive(PartialEq, Debug)]
119pub enum PhysicalBank {
120    /// Flash bank A
121    A,
122    /// Flash bank B
123    B,
124}
125
126#[derive(PartialEq, Debug, Copy, Clone)]
127/// Flash banks identified by which one we boot from.
128///
129/// Memory layout:
130/// ```text
131/// [  Active bank  | Inactive bank ]
132/// ^               ^               ^
133/// 0x0000_0000     flash_size/2    flash_size
134/// ```
135pub enum Bank {
136    /// Bank that is mapped to 0x0000_0000
137    ///
138    /// Active bank occupies first half of the flash memory.
139    Active,
140    /// Bank that is not mapped to 0x0000_0000
141    ///
142    /// Inactive bank occupies second half of the flash memory.
143    Inactive,
144}
145
146impl Bank {
147    /// Provides the address of the bank
148    #[inline]
149    pub fn address(&self) -> u32 {
150        match self {
151            Bank::Active => 0,
152            Bank::Inactive => retrieve_bank_size(),
153        }
154    }
155
156    /// Provides bank length in bytes
157    #[inline]
158    pub fn length(&self) -> u32 {
159        retrieve_bank_size()
160    }
161}
162
163/// NVM result type
164pub type Result<T> = core::result::Result<T, Error>;
165
166impl Nvm {
167    /// Pointer to the userpage region of the flash memory
168    ///
169    /// Note: *Never* call `core::ptr::read_volatile` on this pointer, yields
170    /// very poor codegen (around 9 KiB bytes of code)
171    pub const USERPAGE_ADDR: *const [u8; 512] = 0x0080_4000 as _;
172
173    /// Create a new NVM controller or handle failure from DSU
174    #[inline]
175    pub fn new(nvm: Nvmctrl) -> Self {
176        Self { nvm }
177    }
178
179    /// Raw access to the registers.
180    ///
181    /// # Safety
182    ///
183    /// The abstraction assumes that it has exclusive ownership of the
184    /// registers. Direct access can break such assumptions.
185    pub unsafe fn registers(&self) -> &Nvmctrl {
186        &self.nvm
187    }
188
189    /// Swap the flash banks. The processor will be reset, after which the
190    /// inactive bank will become the active bank.
191    ///
192    /// # Safety
193    ///
194    /// Ensure there is a working, memory safe program in place in the inactive
195    /// bank before calling.
196    #[inline]
197    pub unsafe fn bank_swap(&mut self) -> ! {
198        let _ = self.command_sync(Cmdselect::Bkswrst);
199        // The reset will happen atomically with the rest of the command, so getting
200        // here is an error.
201        unreachable!();
202    }
203
204    /// Set the power reduction mode
205    #[inline]
206    pub fn power_reduction_mode(&mut self, prm: Prmselect) {
207        self.nvm.ctrla().modify(|_, w| w.prm().variant(prm));
208    }
209
210    /// Check if the flash is boot protected
211    #[inline]
212    pub fn is_boot_protected(&self) -> bool {
213        !self.nvm.status().read().bpdis().bit()
214    }
215
216    /// Get first bank
217    #[inline]
218    pub fn first_bank(&self) -> PhysicalBank {
219        if self.nvm.status().read().afirst().bit() {
220            PhysicalBank::A
221        } else {
222            PhysicalBank::B
223        }
224    }
225
226    /// Set address for reading/writing
227    #[inline]
228    fn set_address(&mut self, address: u32) {
229        unsafe {
230            self.nvm
231                .addr()
232                .write(|w| w.addr().bits(address & 0x00ff_ffff));
233        }
234    }
235
236    /// Execute a command, wait until it is done and check error states
237    #[inline]
238    fn command_sync(&mut self, command: Cmdselect) -> Result<()> {
239        // Wait until STATUS.READY
240        while !self.nvm.status().read().ready().bit() {}
241
242        self.nvm
243            .ctrlb()
244            .write(|w| w.cmdex().key().cmd().variant(command));
245
246        // Wait until INTFLAG.DONE
247        while !self.nvm.intflag().read().done().bit() {}
248        // Clear INTFLAG.DONE
249        self.nvm.intflag().write(|w| w.done().set_bit());
250
251        self.manage_error_states()
252    }
253
254    /// Read the peripheral state to check error flags and clear the up
255    /// afterwards
256    #[inline]
257    fn manage_error_states(&mut self) -> Result<()> {
258        let read_intflag = self.nvm.intflag().read();
259        // Check ADDRE and LOCKE first as it is more specific than PROGE
260        let state = if read_intflag.addre().bit_is_set() {
261            Err(Error::Peripheral(PeripheralError::AddressError))
262        } else if read_intflag.locke().bit_is_set() {
263            Err(Error::Peripheral(PeripheralError::LockError))
264        } else if read_intflag.proge().bit_is_set() {
265            Err(Error::Peripheral(PeripheralError::ProgrammingError))
266        } else {
267            Ok(())
268        };
269
270        // Clear error flags
271        self.nvm
272            .intflag()
273            .write(|w| w.addre().set_bit().locke().set_bit().proge().set_bit());
274
275        state
276    }
277
278    /// Read the user page from the flash memory
279    #[inline]
280    pub fn read_userpage(&self) -> Userpage {
281        let mut userpage = RawUserpage([0_u8; 512]);
282        // Safety:
283        // - Nvm is a singleton because it is constructed using the PAC singleton
284        //   Nvmctrl.
285        // - You need Nvm or Nvmctrl to modify the memory, so the &self singleton is
286        //   enough to prevent concurrent modification.
287        // - Underlying [u8; 512] has no reserved bit patterns.
288        // - The pointer is aligned.
289        // Note: userpage is accessed through the iterator in order to avoid poor
290        // codegen for `read_volatile` call on the array pointer.
291        userpage
292            .0
293            .iter_mut()
294            .zip((0..512).map(|i| unsafe {
295                Self::USERPAGE_ADDR
296                    .cast::<u8>()
297                    .wrapping_offset(i)
298                    .read_volatile()
299            }))
300            .for_each(|(l, r)| *l = r);
301        userpage
302    }
303
304    /// Modify the NVM User Page (aka User Row/UROW)
305    ///
306    /// User is expected to provide a closure that modifies the user page
307    /// according to the user's needs.
308    ///
309    /// This method will read the current user page, call the closure on it,
310    /// *erase the page in the flash memory* and *write it* back again.
311    ///
312    /// Erasure and flashing is skipped if the userpage stays the same after
313    /// calling the closure on it.
314    ///
315    /// # Safety
316    ///
317    /// Even though factory calibration settings are not modifiable via setters
318    /// they can be still mutated via raw access to the `userpage.0` field.
319    ///
320    /// Power loss between the erase and the write will result in *data loss*.
321    ///
322    /// Thus, users are advised to *backup factory calibration settings* before
323    /// mutating the user page!
324    ///
325    /// If these settings are erased, device might stop behaving correctly!
326    #[inline]
327    pub unsafe fn modify_userpage(
328        &mut self,
329        f: impl FnOnce(&mut Userpage),
330    ) -> Result<UserpageStatus> {
331        let original = self.read_userpage();
332        let mut modified = original.clone();
333
334        f(&mut modified);
335
336        if original != modified {
337            unsafe { self.erase(NvmErase::Userpage)? };
338            unsafe { self.write(NvmWrite::Userpage(&modified))? };
339
340            Ok(UserpageStatus::Updated)
341        } else {
342            Ok(UserpageStatus::Skipped)
343        }
344    }
345
346    /// Read the calibration area
347    #[inline]
348    pub fn calibration_area(&self) -> CalibrationArea {
349        let mut buffer = 0_u64;
350        let base_addr: *const u8 = 0x0080_0080 as *const u8;
351
352        for i in 0..6 {
353            buffer |=
354                unsafe { core::ptr::read_volatile(base_addr.offset(i as isize)) as u64 } << (i * 8);
355        }
356
357        CalibrationArea(buffer)
358    }
359
360    /// Read the calibration area for temperatures
361    #[inline]
362    pub fn temperatures_calibration_area(&self) -> TemperaturesCalibrationArea {
363        let mut buffer = 0_u128;
364        let base_addr: *const u8 = 0x0080_0100 as *const u8;
365
366        for i in 0..11 {
367            buffer |= unsafe { core::ptr::read_volatile(base_addr.offset(i as isize)) as u128 }
368                << (i * 8);
369        }
370
371        TemperaturesCalibrationArea(buffer)
372    }
373
374    /// Enable security bit
375    ///
376    /// It locks the chip from external access for code security. Consult the
377    /// datasheet for more details.
378    ///
379    /// In order to disable it, chip erase command must be issued through the
380    /// debugger.
381    #[inline]
382    pub fn enable_security_bit(&mut self) -> Result<()> {
383        self.command_sync(Cmdselect::Ssb)
384    }
385
386    /// Enable the chip erase lock
387    ///
388    /// It disables the chip erase capability.
389    ///
390    /// # Safety
391    ///
392    /// Together with [`Self::enable_security_bit`], it completely locks the MCU
393    /// down from any external interaction via debugger, thus effectively
394    /// *bricking the device*. Flashed firmware *must provide a way* to
395    /// execute [`Self::disable_chip_erase_lock`] method in order to enable
396    /// the debugger access again.
397    #[inline]
398    pub unsafe fn enable_chip_erase_lock(&mut self) -> Result<()> {
399        self.command_sync(Cmdselect::Celck)
400    }
401
402    /// Disable the chip erase lock
403    ///
404    /// It enables the chip erase capability through the debugger.
405    #[inline]
406    pub fn disable_chip_erase_lock(&mut self) -> Result<()> {
407        self.command_sync(Cmdselect::Ceulck)
408    }
409
410    /// Enable/disable boot protection
411    ///
412    /// Userpage's NVM BOOT field defines a memory region that is supposed to be
413    /// protected. `Nvmctrl.STATUS.BOOTPROT` is a readonly HW register populated
414    /// on reset with a value from a userpage. By default, 0
415    #[inline]
416    pub fn boot_protection(&mut self, protect: bool) -> Result<()> {
417        // Check if requested state differs from current state
418        if self.is_boot_protected() != protect {
419            // Requires both command and key so the command is allowed to execute
420            if protect {
421                // Issue Clear boot protection disable (enable bootprotection)
422                self.command_sync(Cmdselect::Cbpdis)
423            } else {
424                // Issue Set boot protection disable (disable bootprotection)
425                self.command_sync(Cmdselect::Sbpdis)
426            }
427        } else {
428            Ok(())
429        }
430    }
431
432    /// Enable/disable region lock
433    ///
434    /// Flash memory is split into 32 regions. The 32 bits of the `mask`
435    /// determine if each region should be locked (if its bit is 0) and prevent
436    /// writing and erasing pages, or unlocked (if its bit is 1) and allow
437    /// writing and erasing pages.
438    ///
439    /// Less significant bits represent lower addresses, more significant bits
440    /// represent higher addresses.
441    ///
442    /// For example mask `0xFFFF_0000` implies that active bank should be locked
443    /// while inactive bank should be unlocked.
444    #[inline]
445    pub fn region_lock(&mut self, mask: u32) -> Result<()> {
446        const REGIONS_COUNT: u32 = 32;
447        const FLASH_START: u32 = 0;
448        let flash_end = retrieve_flash_size();
449        let region_size = (flash_end - FLASH_START) / REGIONS_COUNT;
450        for (i, address) in (FLASH_START..flash_end)
451            .step_by(region_size as usize)
452            .enumerate()
453        {
454            self.set_address(address);
455            let protect = mask & (1 << i) == 0;
456            self.command_sync(if protect {
457                Cmdselect::Lr
458            } else {
459                Cmdselect::Ur
460            })?;
461        }
462        Ok(())
463    }
464
465    /// Write to the main address space flash memory from a slice
466    ///
467    /// This call will fail if area that is being written to is
468    /// - outside of the main address space flash area
469    /// - write protected (BOOTPROT)
470    /// - overlapping with SmartEEPROM flash region
471    ///
472    /// `destination` has to be 4 bytes aligned.
473    ///
474    /// # Safety
475    ///
476    /// Writes to the main address space flash area containing currently
477    /// executed application are unsound.
478    #[inline]
479    pub unsafe fn write_flash_from_slice(
480        &mut self,
481        destination: *mut u32,
482        source_slice: &[u32],
483        write_granularity: WriteGranularity,
484    ) -> Result<()> {
485        let source = source_slice.as_ptr();
486        let words = source_slice.len() as u32;
487
488        // Safety: prerequisites bubbled up to the method signature
489        unsafe {
490            self.write(NvmWrite::MainAddressSpace {
491                destination,
492                source,
493                words,
494                write_granularity,
495            })
496        }
497    }
498
499    /// Write to the main address space flash memory
500    ///
501    /// This call will fail if area that is being written to is
502    /// - outside of the main address space flash area
503    /// - write protected (BOOTPROT)
504    /// - overlapping with SmartEEPROM flash region
505    ///
506    /// `destination` has to be 4 bytes aligned.
507    /// `source` has to be 4 bytes aligned.
508    ///
509    /// # Safety
510    ///
511    /// Writes to the main address space flash area containing currently
512    /// executed application are unsound.
513    #[inline]
514    pub unsafe fn write_flash(
515        &mut self,
516        destination: *mut u32,
517        source: *const u32,
518        words: u32,
519        write_granularity: WriteGranularity,
520    ) -> Result<()> {
521        // Safety: prerequisites bubbled up to the method signature
522        unsafe {
523            self.write(NvmWrite::MainAddressSpace {
524                destination,
525                source,
526                words,
527                write_granularity,
528            })
529        }
530    }
531
532    /// Write to the flash memory
533    ///
534    /// Failure modes regarding writes to the main address space flash
535    /// area are mentioned in [`Self::write_flash`] documentation
536    ///
537    /// # Safety
538    ///
539    /// Safety requirements regarding writes to the main address space flash
540    /// area are mentioned in [`Self::write_flash`] documentation
541    ///
542    /// Safety requirements regarding userpage modifications are mentioned in
543    /// [`Self::modify_userpage`] documentation
544    #[inline]
545    unsafe fn write(&mut self, op: NvmWrite) -> Result<()> {
546        let (destination_address, source_address, words, granularity) = match op {
547            NvmWrite::MainAddressSpace {
548                destination,
549                source,
550                words,
551                write_granularity,
552            } => (destination as u32, source as u32, words, write_granularity),
553            NvmWrite::Userpage(userpage) => (
554                Self::USERPAGE_ADDR as u32,
555                addr_of!(userpage.0) as u32,
556                PAGESIZE / core::mem::size_of::<u32>() as u32,
557                WriteGranularity::QuadWord,
558            ),
559        };
560
561        // Length of memory step
562        let step_size = core::mem::size_of::<u32>() as u32;
563        // Length of data in bytes
564        let length = words * step_size;
565        let write_size = granularity.size();
566
567        let read_addresses = source_address..(source_address + length);
568        let write_addresses = destination_address..(destination_address + length);
569
570        if source_address % step_size != 0 {
571            return Err(Error::Alignment);
572        }
573
574        if destination_address % step_size != 0 {
575            return Err(Error::Alignment);
576        }
577
578        match op {
579            NvmWrite::MainAddressSpace { .. } => {
580                if self.contains_non_flash_memory_area(&write_addresses) {
581                    return Err(Error::NonFlash);
582                } else if self.contains_bootprotected(&write_addresses) {
583                    return Err(Error::Protected);
584                } else if self.contains_smart_eeprom(&write_addresses) {
585                    return Err(Error::SmartEepromArea);
586                }
587            }
588            NvmWrite::Userpage(_) => {
589                // Nothing to check
590            }
591        }
592
593        self.command_sync(Cmdselect::Pbc)?;
594        // Track whether we have unwritten data in the page buffer
595        let mut dirty = false;
596        // Zip two iterators, one counter and one with the addr word aligned
597        for (destination_address, source_address) in write_addresses
598            .step_by(step_size as usize)
599            .zip(read_addresses.step_by(step_size as usize))
600        {
601            // Write to memory, 32 bits, 1 word.
602            // The data is placed in the page buffer and ADDR is updated automatically.
603            // Memory is not written until the write page command is issued later.
604            let value = core::ptr::read_volatile(source_address as *const u32);
605            core::ptr::write_volatile(destination_address as *mut u32, value);
606            dirty = true;
607
608            // If we are about to cross a page boundary (and run out of page buffer), write
609            // to flash
610            if destination_address % write_size >= write_size - step_size {
611                // Perform a write
612                self.command_sync(granularity.command())?;
613                dirty = false;
614            }
615        }
616
617        if dirty {
618            // The dirty flag has fulfilled its role here, so we don't bother to maintain
619            // its invariant anymore. Otherwise, the compiler would warn of
620            // unused assignments. Write last page
621            self.command_sync(granularity.command())?
622        }
623
624        Ok(())
625    }
626
627    /// Erase the portion of the main address space flash memory
628    ///
629    /// Erase granularity is expressed in blocks (16 pages == 8192 bytes)
630    ///
631    /// This call will fail if area that is being erased is
632    /// - outside of the main address space flash area
633    /// - write protected (BOOTPROT)
634    /// - overlapping with SmartEEPROM flash region
635    ///
636    /// # Safety
637    ///
638    /// Erasure of the main address space flash area containing currently
639    /// executed application is unsound.
640    #[inline]
641    pub unsafe fn erase_flash(&mut self, address: *mut u32, blocks: u32) -> Result<()> {
642        // Safety: prerequisites bubbled up to the method signature
643        unsafe { self.erase(NvmErase::Flash { address, blocks }) }
644    }
645
646    /// Erase the flash memory.
647    ///
648    /// Failure modes regarding erasure of the main address space flash
649    /// area are mentioned in [`Self::erase_flash`] documentation
650    ///
651    /// # Safety
652    ///
653    /// Safety requirements regarding erasure of the main address space flash
654    /// area are mentioned in [`Self::erase_flash`] documentation
655    ///
656    /// Safety requirements regarding userpage modifications are mentioned in
657    /// [`Self::modify_userpage`] documentation
658    #[inline]
659    unsafe fn erase(&mut self, op: NvmErase) -> Result<()> {
660        let (address, length, granularity) = match op {
661            NvmErase::Flash { address, blocks } => {
662                (address as u32, blocks, EraseGranularity::Block)
663            }
664            NvmErase::Userpage => (Self::USERPAGE_ADDR as u32, 1, EraseGranularity::Page),
665        };
666
667        // Align to block/page boundary
668        // While the NVM will accept any address in the block, we need to compute the
669        // aligned address to check for boot protection.
670        let flash_address = address - address % granularity.size();
671        let range_to_erase = flash_address..(flash_address + length * granularity.size());
672
673        match op {
674            NvmErase::Flash { .. } => {
675                if self.contains_non_flash_memory_area(&range_to_erase) {
676                    return Err(Error::NonFlash);
677                } else if self.contains_bootprotected(&range_to_erase) {
678                    return Err(Error::Protected);
679                } else if self.contains_smart_eeprom(&range_to_erase) {
680                    return Err(Error::SmartEepromArea);
681                }
682            }
683            NvmErase::Userpage => {
684                // Nothing to check
685            }
686        }
687
688        for address in range_to_erase.step_by(granularity.size() as usize) {
689            // Set target address to current block/page offset
690            self.set_address(address);
691
692            // Erase block/page, wait for completion
693            self.command_sync(granularity.command())?
694        }
695
696        Ok(())
697    }
698
699    #[inline]
700    fn contains_bootprotected(&self, input: &Range<u32>) -> bool {
701        // Calculate size that is protected for bootloader
702        //   * 15 = no bootprotection, default value
703        //   * 0 = max bootprotection, 15 * 8Kibyte = 120KiB
704        //   * (15 - bootprot) * 8KiB = protected size
705        let bootprot = self.nvm.status().read().bootprot().bits();
706        let bp_space = 8 * 1024 * (15 - bootprot) as u32;
707
708        let boot = &(Bank::Active.address()..(Bank::Active.address() + bp_space));
709        self.is_boot_protected() && range_overlap(input, boot)
710    }
711
712    #[inline]
713    fn contains_smart_eeprom(&self, input: &Range<u32>) -> bool {
714        let smart_eeprom_allocated_blocks = self.nvm.seestat().read().sblk().bits() as u32;
715        let smart_eeprom_end = Bank::Inactive.address() + Bank::Inactive.length();
716        let smart_eeprom_start = smart_eeprom_end - smart_eeprom_allocated_blocks * BLOCKSIZE;
717        let smart_eeprom = &(smart_eeprom_start..smart_eeprom_end);
718        range_overlap(input, smart_eeprom)
719    }
720
721    #[inline]
722    fn contains_non_flash_memory_area(&self, input: &Range<u32>) -> bool {
723        input.end > retrieve_flash_size()
724    }
725
726    /// Retrieve SmartEEPROM
727    #[inline]
728    pub fn smart_eeprom(&mut self) -> smart_eeprom::Result<'_> {
729        smart_eeprom::SmartEepromMode::retrieve(self)
730    }
731}
732
733/// The outcome of [`Nvm::modify_userpage`]
734#[derive(Copy, Clone, Debug)]
735pub enum UserpageStatus {
736    /// Userpage has been updated
737    Updated,
738    /// Update has been skipped; expected value is already present.
739    Skipped,
740}
741
742enum NvmWrite<'a> {
743    /// Writes to the flash memory within the main address space
744    MainAddressSpace {
745        destination: *mut u32,
746        source: *const u32,
747        words: u32,
748        write_granularity: WriteGranularity,
749    },
750    /// Writes the userpage to the corresponding flash memory
751    Userpage(&'a Userpage),
752}
753
754enum NvmErase {
755    Flash { address: *mut u32, blocks: u32 },
756    Userpage,
757}
758
759/// Data erased per command
760#[derive(Copy, Clone, Debug)]
761enum EraseGranularity {
762    /// One block. This erase type is supported by main memory
763    Block,
764    /// One page. This erase type is supported for the AUX memory
765    Page,
766}
767
768/// Data written per command
769#[derive(Copy, Clone, Debug)]
770pub enum WriteGranularity {
771    /// Four words (16 bytes). Expected for user page writes
772    QuadWord,
773    /// One page (512 bytes)
774    Page,
775}
776
777impl EraseGranularity {
778    #[inline]
779    fn command(&self) -> Cmdselect {
780        match self {
781            Self::Block => Cmdselect::Eb,
782            Self::Page => Cmdselect::Ep,
783        }
784    }
785
786    #[inline]
787    fn size(&self) -> u32 {
788        match self {
789            Self::Block => BLOCKSIZE,
790            Self::Page => PAGESIZE,
791        }
792    }
793}
794
795impl WriteGranularity {
796    #[inline]
797    fn command(&self) -> Cmdselect {
798        match self {
799            Self::QuadWord => Cmdselect::Wqw,
800            Self::Page => Cmdselect::Wp,
801        }
802    }
803
804    #[inline]
805    fn size(&self) -> u32 {
806        match self {
807            Self::QuadWord => QUADWORDSIZE,
808            Self::Page => PAGESIZE,
809        }
810    }
811}
812
813fn range_overlap(a: &Range<u32>, b: &Range<u32>) -> bool {
814    a.start < b.end && b.start < a.end
815}
816
817/// Type alias to the userpage with a concrete underlying storage type
818pub type Userpage = RawUserpage<[u8; 512]>;
819
820bitfield! {
821    /// Raw userpage POD struct that exposes bitfields via methods
822    #[derive(Clone, PartialEq, Eq)]
823    pub struct RawUserpage([u8]);
824    impl Debug;
825    u8;
826    /// Access the `bod33_disable` field
827    pub bod33_disable, set_bod33_disable: 0;
828    /// Access the `bod33_level` field
829    pub bod33_level, set_bod33_level: 8, 1;
830    /// Access the `bod33_action` field
831    pub bod33_action, set_bod33_action: 10, 9;
832    /// Access the `bod33_hysteresis` field
833    pub bod33_hysteresis, set_bod33_hysteresis: 14, 11;
834    /// Access the `bod12_calibration_parameters` field
835    pub u16, bod12_calibration_parameters, set_bod12_calibration_parameters: 25, 15;
836    /// Access the `nvm_bootloader_size` field
837    pub nvm_bootloader_size, set_nvm_bootloader_size: 29, 26;
838    /// Access the `reserved_0` field
839    pub reserved_0, set_reserved_0: 31, 30;
840    /// Access the `see_sblk` field
841    pub see_sblk, set_see_sblk: 35, 32;
842    /// Access the `see_psz` field
843    pub see_psz, set_see_psz: 38, 36;
844    /// Access the `ram_ecc_disable` field
845    pub ram_ecc_disable, set_ram_ecc_disable: 39;
846    /// Access the `reserved_1` field
847    pub reserved_1, set_reserved_1: 47, 40;
848    /// Access the `wdt_enable` field
849    pub wdt_enable, set_wdt_enable: 48;
850    /// Access the `wdt_always_on` field
851    pub wdt_always_on, set_wdt_always_on: 49;
852    /// Access the `wdt_period` field
853    pub wdt_period, set_wdt_period: 53, 50;
854    /// Access the `wdt_window` field
855    pub wdt_window, set_wdt_window: 57, 54;
856    /// Access the `wdt_ewoffset` field
857    pub wdt_ewoffset, set_wdt_ewoffset: 61, 58;
858    /// Access the `wdt_wen` field
859    pub wdt_wen, set_wdt_wen: 62;
860    /// Access the `reserved_2` field
861    pub reserved_2, set_reserved_2: 63;
862    /// Access the `nvm_locks` field
863    pub u32, nvm_locks, set_nvm_locks: 95, 64;
864    /// Access the `userpage_0` field
865    pub u32, userpage_0, set_userpage_0: 127, 96;
866    /// Access the `reserved_3` field
867    pub u32, reserved_3, set_reserved_3: 159, 128;
868}
869
870impl<T: AsRef<[u8]>> RawUserpage<T> {
871    /// Access the general purpose user-writable section of the userpage via
872    /// slice
873    #[inline]
874    pub fn userpage1_as_slice(&self) -> &[u8] {
875        // `userpage1` starts on 160th bit (20th byte) and continues till the end of
876        // the userpage
877        &self.0.as_ref()[20..512]
878    }
879}
880impl<T: AsMut<[u8]>> RawUserpage<T> {
881    /// Access the general purpose user-writable section of the userpage via
882    /// mutable slice
883    #[inline]
884    pub fn userpage1_as_slice_mut(&mut self) -> &mut [u8] {
885        // `userpage1` starts on 160th bit (20th byte) and continues till the end of
886        // the userpage
887        &mut self.0.as_mut()[20..512]
888    }
889}
890
891bitfield! {
892    #[derive(Copy, Clone, Default)]
893    /// POD-style struct representing NVM calibration area
894    pub struct CalibrationArea(u64);
895    impl Debug;
896    u32;
897    /// Access the `ac_bias` field. Setter is not provided.
898    pub ac_bias, _: 1, 0;
899    /// Access the `adc0_biascomp` field. Setter is not provided.
900    pub adc0_biascomp, _: 4, 2;
901    /// Access the `adc0_biasrefbuf` field. Setter is not provided.
902    pub adc0_biasrefbuf, _: 7, 5;
903    /// Access the `adc0_biasr2r` field. Setter is not provided.
904    pub adc0_biasr2r, _: 10, 8;
905    /// Access the `adc1_biascomp` field. Setter is not provided.
906    pub adc1_biascomp, _: 18, 16;
907    /// Access the `adc1_biasrefbuf` field. Setter is not provided.
908    pub adc1_biasrefbuf, _: 21, 19;
909    /// Access the `adc1_biasr2r` field. Setter is not provided.
910    pub adc1_biasr2r, _: 24, 22;
911    /// Access the `usb_transn` field. Setter is not provided.
912    pub usb_transn, _: 36, 32;
913    /// Access the `usb_transp` field. Setter is not provided.
914    pub usb_transp, _: 41, 37;
915    /// Access the `usb_trim` field. Setter is not provided.
916    pub usb_trim, _: 44, 42;
917}
918
919bitfield! {
920    #[derive(Copy, Clone, Default)]
921    /// POD-style struct representing NVM calibration area for
922    /// temperature calibration
923    pub struct TemperaturesCalibrationArea(u128);
924    impl Debug;
925    u32;
926    /// Access the `tli` field. Setter is not provided.
927    pub tli, _: 7, 0;
928    /// Access the `tld` field. Setter is not provided.
929    pub tld, _: 11, 8;
930    /// Access the `thi` field. Setter is not provided.
931    pub thi, _: 19, 12;
932    /// Access the `thd` field. Setter is not provided.
933    pub thd, _: 23, 20;
934    /// Access the `vpl` field. Setter is not provided.
935    pub vpl, _: 51, 40;
936    /// Access the `vph` field. Setter is not provided.
937    pub vph, _: 63, 52;
938    /// Access the `vcl` field. Setter is not provided.
939    pub vcl, _: 75, 63;
940    /// Access the `vch` field. Setter is not provided.
941    pub vch, _: 87, 76;
942}