va416xx_hal/
nvm.rs

1//! Non-volatile memory (NVM) driver.
2//!
3//! Provides a basic API to work with the internal NVM of the VA41630 MCU.
4//!
5//! # Examples
6//!
7//! - [Flashloader application](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/flashloader)
8use embedded_hal::spi::MODE_0;
9use vorago_shared_hal::{
10    disable_peripheral_clock, enable_peripheral_clock, reset_peripheral_for_cycles,
11};
12
13use crate::clock::Clocks;
14use crate::pac;
15use crate::spi::{
16    mode_to_cpo_cph_bit, spi_clk_config_from_div, SpiInstance, SpiWord, BMSTART_BMSTOP_MASK,
17};
18
19const NVM_CLOCK_DIV: u16 = 2;
20
21// Commands. The internal FRAM is based on the Cypress FM25V20A device.
22
23/// Write enable register.
24pub const FRAM_WREN: u8 = 0x06;
25pub const FRAM_WRDI: u8 = 0x04;
26pub const FRAM_RDSR: u8 = 0x05;
27/// Write single status register
28pub const FRAM_WRSR: u8 = 0x01;
29pub const FRAM_READ: u8 = 0x03;
30pub const FRAM_WRITE: u8 = 0x02;
31pub const FRAM_RDID: u8 = 0x9F;
32pub const FRAM_SLEEP: u8 = 0xB9;
33
34// Address Masks
35
36const ADDR_MSB_MASK: u32 = 0xFF0000;
37const ADDR_MID_MASK: u32 = 0x00FF00;
38const ADDR_LSB_MASK: u32 = 0x0000FF;
39
40#[inline(always)]
41const fn msb_addr_byte(addr: u32) -> u8 {
42    ((addr & ADDR_MSB_MASK) >> 16) as u8
43}
44
45#[inline(always)]
46const fn mid_addr_byte(addr: u32) -> u8 {
47    ((addr & ADDR_MID_MASK) >> 8) as u8
48}
49
50#[inline(always)]
51const fn lsb_addr_byte(addr: u32) -> u8 {
52    (addr & ADDR_LSB_MASK) as u8
53}
54
55pub const WPEN_ENABLE_MASK: u8 = 1 << 7;
56pub const BP_0_ENABLE_MASK: u8 = 1 << 2;
57pub const BP_1_ENABLE_MASK: u8 = 1 << 3;
58
59pub struct Nvm {
60    spi: Option<pac::Spi3>,
61}
62
63#[derive(Debug, PartialEq, Eq)]
64#[cfg_attr(feature = "defmt", derive(defmt::Format))]
65pub struct VerifyError {
66    addr: u32,
67    found: u8,
68    expected: u8,
69}
70
71impl Nvm {
72    pub fn new(spi: pac::Spi3, _clocks: &Clocks) -> Self {
73        enable_peripheral_clock(pac::Spi3::PERIPH_SEL);
74        // This is done in the C HAL.
75        reset_peripheral_for_cycles(pac::Spi3::PERIPH_SEL, 2);
76
77        let spi_clk_cfg = spi_clk_config_from_div(NVM_CLOCK_DIV).unwrap();
78        let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(MODE_0);
79        spi.ctrl0().write(|w| {
80            unsafe {
81                w.size().bits(u8::word_reg());
82                w.scrdv().bits(spi_clk_cfg.scrdv());
83                // Clear clock phase and polarity. Will be set to correct value for each
84                // transfer
85                w.spo().bit(cpo_bit);
86                w.sph().bit(cph_bit)
87            }
88        });
89        spi.ctrl1().write(|w| {
90            w.blockmode().set_bit();
91            unsafe { w.ss().bits(0) };
92            w.bmstart().set_bit();
93            w.bmstall().set_bit()
94        });
95        spi.clkprescale()
96            .write(|w| unsafe { w.bits(spi_clk_cfg.prescale_val() as u32) });
97
98        spi.fifo_clr().write(|w| {
99            w.rxfifo().set_bit();
100            w.txfifo().set_bit()
101        });
102        // Enable the peripheral as the last step as recommended in the
103        // programmers guide
104        spi.ctrl1().modify(|_, w| w.enable().set_bit());
105
106        let mut nvm = Self { spi: Some(spi) };
107        nvm.disable_write_prot();
108        nvm
109    }
110
111    pub fn disable_write_prot(&mut self) {
112        self.wait_for_tx_idle();
113        self.write_with_bmstop(FRAM_WREN);
114        self.wait_for_tx_idle();
115        self.write_single(FRAM_WRSR);
116        self.write_with_bmstop(0x00);
117        self.wait_for_tx_idle();
118    }
119
120    pub fn read_rdsr(&self) -> u8 {
121        self.write_single(FRAM_RDSR);
122        self.write_with_bmstop(0x00);
123        self.wait_for_rx_available();
124        self.read_single_word();
125        self.wait_for_rx_available();
126        (self.read_single_word() & 0xff) as u8
127    }
128
129    pub fn enable_write_prot(&mut self) {
130        self.wait_for_tx_idle();
131        self.write_with_bmstop(FRAM_WREN);
132        self.wait_for_tx_idle();
133        self.write_single(FRAM_WRSR);
134        self.write_with_bmstop(0x00);
135    }
136    #[inline(always)]
137    pub fn spi(&self) -> &pac::Spi3 {
138        self.spi.as_ref().unwrap()
139    }
140
141    #[inline(always)]
142    pub fn write_single(&self, word: u8) {
143        self.spi().data().write(|w| unsafe { w.bits(word as u32) });
144    }
145
146    #[inline(always)]
147    pub fn write_with_bmstop(&self, word: u8) {
148        self.spi()
149            .data()
150            .write(|w| unsafe { w.bits(BMSTART_BMSTOP_MASK | word as u32) });
151    }
152
153    #[inline(always)]
154    pub fn wait_for_tx_idle(&self) {
155        while self.spi().status().read().tfe().bit_is_clear() {
156            cortex_m::asm::nop();
157        }
158        while self.spi().status().read().busy().bit_is_set() {
159            cortex_m::asm::nop();
160        }
161        self.clear_fifos()
162    }
163
164    #[inline(always)]
165    pub fn clear_fifos(&self) {
166        self.spi().fifo_clr().write(|w| {
167            w.rxfifo().set_bit();
168            w.txfifo().set_bit()
169        });
170    }
171
172    #[inline(always)]
173    pub fn wait_for_rx_available(&self) {
174        while !self.spi().status().read().rne().bit_is_set() {
175            cortex_m::asm::nop();
176        }
177    }
178
179    #[inline(always)]
180    pub fn read_single_word(&self) -> u32 {
181        self.spi().data().read().bits()
182    }
183
184    pub fn write_data(&self, addr: u32, data: &[u8]) {
185        self.wait_for_tx_idle();
186        self.write_with_bmstop(FRAM_WREN);
187        self.wait_for_tx_idle();
188        self.write_single(FRAM_WRITE);
189        self.write_single(msb_addr_byte(addr));
190        self.write_single(mid_addr_byte(addr));
191        self.write_single(lsb_addr_byte(addr));
192        for byte in data.iter().take(data.len() - 1) {
193            while self.spi().status().read().tnf().bit_is_clear() {
194                cortex_m::asm::nop();
195            }
196            self.write_single(*byte);
197            self.read_single_word();
198        }
199        while self.spi().status().read().tnf().bit_is_clear() {
200            cortex_m::asm::nop();
201        }
202        self.write_with_bmstop(*data.last().unwrap());
203        self.wait_for_tx_idle();
204    }
205
206    pub fn read_data(&self, addr: u32, buf: &mut [u8]) {
207        self.common_read_start(addr);
208        for byte in buf {
209            // Pump the SPI.
210            self.write_single(0);
211            self.wait_for_rx_available();
212            *byte = self.read_single_word() as u8;
213        }
214        self.write_with_bmstop(0);
215        self.wait_for_tx_idle();
216    }
217
218    pub fn verify_data(&self, addr: u32, comp_buf: &[u8]) -> Result<(), VerifyError> {
219        self.common_read_start(addr);
220        for (idx, byte) in comp_buf.iter().enumerate() {
221            // Pump the SPI.
222            self.write_single(0);
223            self.wait_for_rx_available();
224            let next_word = self.read_single_word() as u8;
225            if next_word != *byte {
226                self.write_with_bmstop(0);
227                self.wait_for_tx_idle();
228                return Err(VerifyError {
229                    addr: addr + idx as u32,
230                    found: next_word,
231                    expected: *byte,
232                });
233            }
234        }
235        self.write_with_bmstop(0);
236        self.wait_for_tx_idle();
237        Ok(())
238    }
239
240    /// Enable write-protection and disables the peripheral clock.
241    pub fn shutdown(&mut self) {
242        self.wait_for_tx_idle();
243        self.write_with_bmstop(FRAM_WREN);
244        self.wait_for_tx_idle();
245        self.write_single(WPEN_ENABLE_MASK | BP_0_ENABLE_MASK | BP_1_ENABLE_MASK);
246        disable_peripheral_clock(pac::Spi3::PERIPH_SEL);
247    }
248
249    /// This function calls [Self::shutdown] and gives back the peripheral structure.
250    pub fn release(mut self) -> pac::Spi3 {
251        self.shutdown();
252        self.spi.take().unwrap()
253    }
254
255    fn common_read_start(&self, addr: u32) {
256        self.wait_for_tx_idle();
257        self.write_single(FRAM_READ);
258        self.write_single(msb_addr_byte(addr));
259        self.write_single(mid_addr_byte(addr));
260        self.write_single(lsb_addr_byte(addr));
261        for _ in 0..4 {
262            // Pump the SPI.
263            self.write_single(0);
264            self.wait_for_rx_available();
265            // The first 4 data bytes received need to be ignored.
266            self.read_single_word();
267        }
268    }
269}
270
271/// Call [Self::shutdown] on drop.
272impl Drop for Nvm {
273    fn drop(&mut self) {
274        if self.spi.is_some() {
275            self.shutdown();
276        }
277    }
278}