diff --git a/Cargo.toml b/Cargo.toml index 2133741..fd5b004 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "examples/rtic", "examples/embassy", "board-tests", + "bootloader", ] exclude = [ @@ -19,7 +20,8 @@ codegen-units = 1 debug = 2 debug-assertions = true # <- incremental = false -opt-level = 'z' # <- +# This is problematic for stepping.. +# opt-level = 'z' # <- overflow-checks = true # <- # cargo build/run --release @@ -31,3 +33,12 @@ incremental = false lto = 'fat' opt-level = 3 # <- overflow-checks = false # <- + +[profile.small] +inherits = "release" +codegen-units = 1 +debug-assertions = false # <- +lto = true +opt-level = 'z' # <- +overflow-checks = false # <- +strip = true # Automatically strip symbols from the binary. diff --git a/bootloader/Cargo.toml b/bootloader/Cargo.toml new file mode 100644 index 0000000..9bef200 --- /dev/null +++ b/bootloader/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "bootloader" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +embedded-hal = "1" +embedded-hal-bus = "0.2" +dummy-pin = "1" +panic-rtt-target = { version = "0.1.3" } +panic-halt = { version = "0.2" } +rtt-target = { version = "0.5" } +crc = "3" + +[dependencies.va108xx-hal] +path = "../va108xx-hal" + +[dependencies.vorago-reb1] +path = "../vorago-reb1" + +[features] +default = [] +rtt-panic = [] diff --git a/bootloader/src/main.rs b/bootloader/src/main.rs new file mode 100644 index 0000000..bc28aba --- /dev/null +++ b/bootloader/src/main.rs @@ -0,0 +1,312 @@ +//! Vorago bootloader which can boot from two images. +//! +//! Bootloader memory map +//! +//! * <0x0> Bootloader start +//! * <0x3FFE> Bootloader CRC +//! * <0x4000> App image A start +//! * <0x21FFC> App image A CRC check length +//! * <0x21FFE> App image A CRC check value +//! * <0x22000> App image B start +//! * <0x3FFFC> App image B CRC check length +//! * <0x3FFFE> App image B CRC check value +//! * <0x40000> +//! +//! As opposed to the Vorago example code, this bootloader assumes a 40 MHz external clock +//! but does not scale that clock up. +#![no_main] +#![no_std] +use cortex_m_rt::entry; +use crc::{Crc, CRC_16_IBM_3740}; +use embedded_hal_bus::spi::{ExclusiveDevice, NoDelay}; +#[cfg(not(feature = "rtt-panic"))] +use panic_halt as _; +#[cfg(feature = "rtt-panic")] +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; +use va108xx_hal::{ + pac, + spi::{RomMiso, RomMosi, RomSck, Spi, SpiClkConfig, SpiConfig}, + time::Hertz, +}; +use vorago_reb1::m95m01::M95M01; + +// Useful for debugging and see what the bootloader is doing. Enabled currently, because +// the binary stays small enough. +const RTT_PRINTOUT: bool = true; +const DEBUG_PRINTOUTS: bool = false; + +// Dangerous option! An image with this option set to true will flash itself from RAM directly +// into the NVM. This can be used as a recovery option from a direct RAM flash to fix the NVM +// boot process. Please note that this will flash an image which will also always perform the +// self-flash itself. It is recommended that you use a tool like probe-rs, Keil IDE, or a flash +// loader to boot a bootloader without this feature. +const FLASH_SELF: bool = false; + +// Register definitions for Cortex-M0 SCB register. +pub const SCB_AIRCR_VECTKEY_POS: u32 = 16; +pub const SCB_AIRCR_VECTKEY_MSK: u32 = 0xFFFF << SCB_AIRCR_VECTKEY_POS; + +pub const SCB_AIRCR_SYSRESETREQ_POS: u32 = 2; +pub const SCB_AIRCR_SYSRESETREQ_MSK: u32 = 1 << SCB_AIRCR_SYSRESETREQ_POS; + +const CLOCK_FREQ: Hertz = Hertz::from_raw(50_000_000); + +// Important bootloader addresses and offsets, vector table information. + +const BOOTLOADER_START_ADDR: u32 = 0x0; +const BOOTLOADER_CRC_ADDR: u32 = BOOTLOADER_END_ADDR - 2; +// This is also the maximum size of the bootloader. +const BOOTLOADER_END_ADDR: u32 = 0x3000; +const APP_A_START_ADDR: u32 = 0x3000; +const APP_A_SIZE_ADDR: u32 = APP_A_END_ADDR - 8; +// Four bytes reserved, even when only 2 byte CRC is used. Leaves flexibility to switch to CRC32. +const APP_A_CRC_ADDR: u32 = APP_A_END_ADDR - 4; +pub const APP_A_END_ADDR: u32 = 0x11000; +// The actual size of the image which is relevant for CRC calculation. +const APP_B_START_ADDR: u32 = 0x11000; +// The actual size of the image which is relevant for CRC calculation. +const APP_B_SIZE_ADDR: u32 = APP_B_END_ADDR - 8; +// Four bytes reserved, even when only 2 byte CRC is used. Leaves flexibility to switch to CRC32. +const APP_B_CRC_ADDR: u32 = APP_B_END_ADDR - 4; +pub const APP_B_END_ADDR: u32 = 0x20000; +pub const APP_IMG_SZ: u32 = 0xE800; + +pub const VECTOR_TABLE_OFFSET: u32 = 0x0; +pub const VECTOR_TABLE_LEN: u32 = 0xC0; +pub const RESET_VECTOR_OFFSET: u32 = 0x4; + +const CRC_ALGO: Crc = Crc::::new(&CRC_16_IBM_3740); + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum AppSel { + A, + B, +} + +/// Complex type, but this is the price we pay for nice abstraction. It is also very explicit. +pub type Nvm = M95M01< + ExclusiveDevice, dummy_pin::DummyPin, NoDelay>, +>; + +#[entry] +fn main() -> ! { + if RTT_PRINTOUT { + rtt_init_print!(); + rprintln!("-- VA108xx bootloader --"); + } + let mut dp = pac::Peripherals::take().unwrap(); + let cp = cortex_m::Peripherals::take().unwrap(); + + let spi = Spi::new( + &mut dp.sysconfig, + CLOCK_FREQ, + dp.spic, + (RomSck, RomMiso, RomMosi), + // These values are taken from the vorago bootloader app, don't want to experiment here.. + SpiConfig::default().clk_cfg(SpiClkConfig::new(2, 4)), + None, + ); + let mut nvm = + M95M01::new(ExclusiveDevice::new_no_delay(spi, dummy_pin::DummyPin::new_low()).unwrap()) + .expect("creating NVM structure failed"); + + if FLASH_SELF { + let mut first_four_bytes: [u8; 4] = [0; 4]; + read_four_bytes_at_addr_zero(&mut first_four_bytes); + let bootloader_data = { + unsafe { + &*core::ptr::slice_from_raw_parts( + (BOOTLOADER_START_ADDR + 4) as *const u8, + (BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 6) as usize, + ) + } + }; + let mut digest = CRC_ALGO.digest(); + digest.update(&first_four_bytes); + digest.update(bootloader_data); + let bootloader_crc = digest.finalize(); + + nvm.write(0x0, &first_four_bytes) + .expect("writing to NVM failed"); + nvm.write(0x4, bootloader_data) + .expect("writing to NVM failed"); + if let Err(e) = nvm.verify(0x0, &first_four_bytes) { + if RTT_PRINTOUT { + rprintln!("verification of self-flash to NVM failed: {:?}", e); + } + } + if let Err(e) = nvm.verify(0x4, bootloader_data) { + if RTT_PRINTOUT { + rprintln!("verification of self-flash to NVM failed: {:?}", e); + } + } + + nvm.write(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()) + .expect("writing CRC failed"); + if let Err(e) = nvm.verify(BOOTLOADER_CRC_ADDR, &bootloader_crc.to_be_bytes()) { + if RTT_PRINTOUT { + rprintln!( + "error: CRC verification for bootloader self-flash failed: {:?}", + e + ); + } + } + } + + // Check bootloader's CRC (and write it if blank) + check_own_crc(&dp.sysconfig, &cp, &mut nvm); + + if check_app_crc(AppSel::A) { + boot_app(&dp.sysconfig, &cp, AppSel::A) + } else if check_app_crc(AppSel::B) { + boot_app(&dp.sysconfig, &cp, AppSel::B) + } else { + if DEBUG_PRINTOUTS && RTT_PRINTOUT { + rprintln!("both images corrupt! booting image A"); + } + // TODO: Shift a CCSDS packet out to inform host/OBC about image corruption. + // Both images seem to be corrupt. Boot default image A. + boot_app(&dp.sysconfig, &cp, AppSel::A) + } +} + +fn check_own_crc(sysconfig: &pac::Sysconfig, cp: &cortex_m::Peripherals, nvm: &mut Nvm) { + let crc_exp = unsafe { (BOOTLOADER_CRC_ADDR as *const u16).read_unaligned().to_be() }; + // I'd prefer to use [core::slice::from_raw_parts], but that is problematic + // because the address of the bootloader is 0x0, so the NULL check fails and the functions + // panics. + let mut first_four_bytes: [u8; 4] = [0; 4]; + read_four_bytes_at_addr_zero(&mut first_four_bytes); + let mut digest = CRC_ALGO.digest(); + digest.update(&first_four_bytes); + digest.update(unsafe { + &*core::ptr::slice_from_raw_parts( + (BOOTLOADER_START_ADDR + 4) as *const u8, + (BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 6) as usize, + ) + }); + let crc_calc = digest.finalize(); + if crc_exp == 0x0000 || crc_exp == 0xffff { + if DEBUG_PRINTOUTS && RTT_PRINTOUT { + rprintln!("BL CRC blank - prog new CRC"); + } + // Blank CRC, write it to NVM. + nvm.write(BOOTLOADER_CRC_ADDR, &crc_calc.to_be_bytes()) + .expect("writing CRC failed"); + // The Vorago bootloader resets here. I am not sure why this is done but I think it is + // necessary because somehow the boot will not work if we just continue as usual. + // cortex_m::peripheral::SCB::sys_reset(); + } else if crc_exp != crc_calc { + // Bootloader is corrupted. Try to run App A. + if DEBUG_PRINTOUTS && RTT_PRINTOUT { + rprintln!( + "bootloader CRC corrupt, read {} and expected {}. booting image A immediately", + crc_calc, + crc_exp + ); + } + // TODO: Shift out minimal CCSDS frame to notify about bootloader corruption. + boot_app(sysconfig, cp, AppSel::A); + } +} + +// Reading from address 0x0 is problematic in Rust. +// See https://users.rust-lang.org/t/reading-from-physical-address-0x0/117408/5. +// This solution falls back to assembler to deal with this. +fn read_four_bytes_at_addr_zero(buf: &mut [u8; 4]) { + unsafe { + core::arch::asm!( + "ldr r0, [{0}]", // Load 4 bytes from src into r0 register + "str r0, [{1}]", // Store r0 register into first_four_bytes + in(reg) BOOTLOADER_START_ADDR as *const u8, // Input: src pointer (0x0) + in(reg) buf as *mut [u8; 4], // Input: destination pointer + ); + } +} +fn check_app_crc(app_sel: AppSel) -> bool { + if DEBUG_PRINTOUTS && RTT_PRINTOUT { + rprintln!("Checking image {:?}", app_sel); + } + if app_sel == AppSel::A { + check_app_given_addr(APP_A_CRC_ADDR, APP_A_START_ADDR, APP_A_SIZE_ADDR) + } else { + check_app_given_addr(APP_B_CRC_ADDR, APP_B_START_ADDR, APP_B_SIZE_ADDR) + } +} + +fn check_app_given_addr(crc_addr: u32, start_addr: u32, image_size_addr: u32) -> bool { + let crc_exp = unsafe { (crc_addr as *const u16).read_unaligned().to_be() }; + let image_size = unsafe { (image_size_addr as *const u32).read_unaligned().to_be() }; + // Sanity check. + if image_size > APP_A_END_ADDR - APP_A_START_ADDR - 8 { + if RTT_PRINTOUT { + rprintln!("detected invalid app size {}", image_size); + } + return false; + } + let crc_calc = CRC_ALGO.checksum(unsafe { + core::slice::from_raw_parts(start_addr as *const u8, image_size as usize) + }); + if crc_calc == crc_exp { + return true; + } + false +} + +// The boot works by copying the interrupt vector table (IVT) of the respective app to the +// base address in code RAM (0x0) and then performing a soft reset. +fn boot_app(syscfg: &pac::Sysconfig, cp: &cortex_m::Peripherals, app_sel: AppSel) -> ! { + if DEBUG_PRINTOUTS && RTT_PRINTOUT { + rprintln!("booting app {:?}", app_sel); + } + // Disable ROM protection. + syscfg.rom_prot().write(|w| unsafe { w.bits(1) }); + let base_addr = if app_sel == AppSel::A { + APP_A_START_ADDR + } else { + APP_B_START_ADDR + }; + // Clear all interrupts set. + unsafe { + cp.NVIC.icer[0].write(0xFFFFFFFF); + cp.NVIC.icpr[0].write(0xFFFFFFFF); + + // First 4 bytes done with inline assembly, writing to the physical address 0x0 can not + // be done without it. See https://users.rust-lang.org/t/reading-from-physical-address-0x0/117408/2. + core::ptr::read(base_addr as *const u32); + core::arch::asm!( + "str {0}, [{1}]", // Load 4 bytes from src into r0 register + in(reg) base_addr, // Input: App vector table. + in(reg) BOOTLOADER_START_ADDR as *mut u32, // Input: destination pointer + ); + core::slice::from_raw_parts_mut( + (BOOTLOADER_START_ADDR + 4) as *mut u32, + (VECTOR_TABLE_LEN - 4) as usize, + ) + .copy_from_slice(core::slice::from_raw_parts( + (base_addr + 4) as *const u32, + (VECTOR_TABLE_LEN - 4) as usize, + )); + } + /* Disable re-loading from FRAM/code ROM on soft reset */ + syscfg + .rst_cntl_rom() + .modify(|_, w| w.sysrstreq().clear_bit()); + soft_reset(cp); +} + +// Soft reset based on https://github.com/ARM-software/CMSIS_6/blob/5782d6f8057906d360f4b95ec08a2354afe5c9b9/CMSIS/Core/Include/core_cm0.h#L874. +fn soft_reset(cp: &cortex_m::Peripherals) -> ! { + // Ensure all outstanding memory accesses included buffered write are completed before reset. + cortex_m::asm::dsb(); + unsafe { + cp.SCB + .aircr + .write((0x5FA << SCB_AIRCR_VECTKEY_POS) | SCB_AIRCR_SYSRESETREQ_MSK); + } + // Ensure completion of memory access. + cortex_m::asm::dsb(); + + unreachable!(); +} diff --git a/examples/rtic/Cargo.toml b/examples/rtic/Cargo.toml index e9d4992..be520eb 100644 --- a/examples/rtic/Cargo.toml +++ b/examples/rtic/Cargo.toml @@ -17,6 +17,9 @@ panic-rtt-target = { version = "0.1" } [dependencies.va108xx-hal] path = "../../va108xx-hal" +[dependencies.vorago-reb1] +path = "../../vorago-reb1" + [dependencies.rtic] version = "2" features = ["thumbv6-backend"] diff --git a/vorago-reb1/examples/blinky-button-rtic.rs b/examples/rtic/src/bin/blinky-button-rtic.rs similarity index 81% rename from vorago-reb1/examples/blinky-button-rtic.rs rename to examples/rtic/src/bin/blinky-button-rtic.rs index c869cb7..e844230 100644 --- a/vorago-reb1/examples/blinky-button-rtic.rs +++ b/examples/rtic/src/bin/blinky-button-rtic.rs @@ -5,7 +5,7 @@ #[rtic::app(device = pac)] mod app { use panic_rtt_target as _; - use rtic_monotonics::systick::Systick; + use rtic_example::SYSCLK_FREQ; use rtt_target::{rprintln, rtt_init_default, set_print_channel}; use va108xx_hal::{ clock::{set_clk_div_register, FilterClkSel}, @@ -17,6 +17,8 @@ mod app { use vorago_reb1::button::Button; use vorago_reb1::leds::Leds; + rtic_monotonics::systick_monotonic!(Mono, 1_000); + #[derive(Debug, PartialEq)] pub enum PressMode { Toggle, @@ -44,17 +46,11 @@ mod app { struct Shared {} #[init] - fn init(ctx: init::Context) -> (Shared, Local) { + fn init(cx: init::Context) -> (Shared, Local) { let channels = rtt_init_default!(); set_print_channel(channels.up.0); rprintln!("-- Vorago Button IRQ Example --"); - // Initialize the systick interrupt & obtain the token to prove that we did - let systick_mono_token = rtic_monotonics::create_systick_token!(); - Systick::start( - ctx.core.SYST, - Hertz::from(50.MHz()).raw(), - systick_mono_token, - ); + Mono::start(cx.core.SYST, SYSCLK_FREQ.raw()); let mode = match CFG_MODE { // Ask mode from user via RTT @@ -64,7 +60,7 @@ mod app { }; rprintln!("Using {:?} mode", mode); - let mut dp = ctx.device; + let mut dp = cx.device; let pinsa = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta); let edge_irq = match mode { PressMode::Toggle => InterruptEdge::HighToLow, @@ -117,12 +113,10 @@ mod app { let mode = cx.local.mode; if *mode == PressMode::Toggle { leds[0].toggle(); + } else if button.released() { + leds[0].off(); } else { - if button.released() { - leds[0].off(); - } else { - leds[0].on(); - } + leds[0].on(); } } @@ -138,14 +132,11 @@ mod app { let mut read; loop { read = down_channel.read(&mut read_buf); - for i in 0..read { - let val = read_buf[i] as char; - if val == '0' || val == '1' { - return if val == '0' { - PressMode::Toggle - } else { - PressMode::Keep - }; + for &byte in &read_buf[..read] { + match byte as char { + '0' => return PressMode::Toggle, + '1' => return PressMode::Keep, + _ => continue, // Ignore other characters } } } diff --git a/examples/simple/Cargo.toml b/examples/simple/Cargo.toml index d7052f5..8dae104 100644 --- a/examples/simple/Cargo.toml +++ b/examples/simple/Cargo.toml @@ -18,3 +18,6 @@ cortex-m-semihosting = "0.5.0" [dependencies.va108xx-hal] path = "../../va108xx-hal" features = ["rt", "defmt"] + +[dependencies.vorago-reb1] +path = "../../vorago-reb1" diff --git a/examples/simple/examples/spi.rs b/examples/simple/examples/spi.rs index f776b1e..9416894 100644 --- a/examples/simple/examples/spi.rs +++ b/examples/simple/examples/spi.rs @@ -16,7 +16,7 @@ use va108xx_hal::{ pac::{self, interrupt}, prelude::*, pwm::{default_ms_irq_handler, set_up_ms_tick}, - spi::{self, Spi, SpiBase, TransferConfig}, + spi::{self, Spi, SpiBase, SpiClkConfig, TransferConfigWithHwcs}, IrqCfg, }; @@ -55,6 +55,8 @@ fn main() -> ! { dp.tim0, ); + let spi_clk_cfg = SpiClkConfig::from_clk(50.MHz(), SPI_SPEED_KHZ.kHz()) + .expect("creating SPI clock config failed"); let spia_ref: RefCell>> = RefCell::new(None); let spib_ref: RefCell>> = RefCell::new(None); let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta); @@ -123,17 +125,21 @@ fn main() -> ! { match SPI_BUS_SEL { SpiBusSelect::SpiAPortA | SpiBusSelect::SpiAPortB => { if let Some(ref mut spi) = *spia_ref.borrow_mut() { - let transfer_cfg = - TransferConfig::new_no_hw_cs(SPI_SPEED_KHZ.kHz(), SPI_MODE, BLOCKMODE, false); + let transfer_cfg = TransferConfigWithHwcs::new_no_hw_cs( + Some(spi_clk_cfg), + Some(SPI_MODE), + BLOCKMODE, + false, + ); spi.cfg_transfer(&transfer_cfg); } } SpiBusSelect::SpiBPortB => { if let Some(ref mut spi) = *spib_ref.borrow_mut() { let hw_cs_pin = pinsb.pb2.into_funsel_1(); - let transfer_cfg = TransferConfig::new( - SPI_SPEED_KHZ.kHz(), - SPI_MODE, + let transfer_cfg = TransferConfigWithHwcs::new( + Some(spi_clk_cfg), + Some(SPI_MODE), Some(hw_cs_pin), BLOCKMODE, false, diff --git a/va108xx-hal/src/spi.rs b/va108xx-hal/src/spi.rs index e05a3a7..544fafa 100644 --- a/va108xx-hal/src/spi.rs +++ b/va108xx-hal/src/spi.rs @@ -26,6 +26,8 @@ use embedded_hal::spi::{Mode, MODE_0, MODE_1, MODE_2, MODE_3}; // FIFO has a depth of 16. const FILL_DEPTH: usize = 12; +pub const DEFAULT_CLK_DIV: u16 = 2; + #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum HwChipSelectId { Id0 = 0, @@ -221,6 +223,17 @@ hw_cs_pins!( // SPIC +pub struct RomSck; +pub struct RomMosi; +pub struct RomMiso; + +impl Sealed for RomSck {} +impl PinSck for RomSck {} +impl Sealed for RomMosi {} +impl PinMosi for RomMosi {} +impl Sealed for RomMiso {} +impl PinMiso for RomMiso {} + hw_cs_pins!( pac::Spic, SpiPort::Portc: (PB9, AltFunc3, HwChipSelectId::Id1, HwCs1SpiCPortB0), @@ -241,35 +254,28 @@ hw_cs_pins!( // Config //================================================================================================== -pub trait GenericTransferConfig { +pub trait TransferConfigProvider { fn sod(&mut self, sod: bool); fn blockmode(&mut self, blockmode: bool); fn mode(&mut self, mode: Mode); - fn frequency(&mut self, spi_clk: Hertz); + fn clk_cfg(&mut self, clk_cfg: SpiClkConfig); fn hw_cs_id(&self) -> u8; } /// This struct contains all configuration parameter which are transfer specific /// and might change for transfers to different SPI slaves -#[derive(Copy, Clone)] -pub struct TransferConfig { - pub spi_clk: Hertz, - pub mode: Mode, - /// This only works if the Slave Output Disable (SOD) bit of the [`SpiConfig`] is set to - /// false +#[derive(Copy, Clone, Debug)] +pub struct TransferConfigWithHwcs { pub hw_cs: Option, - pub sod: bool, - /// If this is enabled, all data in the FIFO is transmitted in a single frame unless - /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the - /// duration of multiple data words - pub blockmode: bool, + pub cfg: CommonTransferConfig, } /// Type erased variant of the transfer configuration. This is required to avoid generics in /// the SPI constructor. -pub struct ReducedTransferConfig { - pub spi_clk: Hertz, - pub mode: Mode, +#[derive(Copy, Clone, Debug)] +pub struct CommonTransferConfig { + pub clk_cfg: Option, + pub mode: Option, pub sod: bool, /// If this is enabled, all data in the FIFO is transmitted in a single frame unless /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the @@ -278,62 +284,67 @@ pub struct ReducedTransferConfig { pub hw_cs: HwChipSelectId, } -impl TransferConfig { - pub fn new_no_hw_cs(spi_clk: impl Into, mode: Mode, blockmode: bool, sod: bool) -> Self { - TransferConfig { - spi_clk: spi_clk.into(), - mode, +impl TransferConfigWithHwcs { + pub fn new_no_hw_cs( + clk_cfg: Option, + mode: Option, + blockmode: bool, + sod: bool, + ) -> Self { + TransferConfigWithHwcs { hw_cs: None, - sod, - blockmode, + cfg: CommonTransferConfig { + clk_cfg, + mode, + sod, + blockmode, + hw_cs: HwChipSelectId::Invalid, + }, } } } -impl TransferConfig { +impl TransferConfigWithHwcs { pub fn new( - spi_clk: impl Into, - mode: Mode, + clk_cfg: Option, + mode: Option, hw_cs: Option, blockmode: bool, sod: bool, ) -> Self { - TransferConfig { - spi_clk: spi_clk.into(), - mode, + TransferConfigWithHwcs { hw_cs, - sod, - blockmode, + cfg: CommonTransferConfig { + clk_cfg, + mode, + sod, + blockmode, + hw_cs: HwCs::CS_ID, + }, } } - pub fn downgrade(self) -> ReducedTransferConfig { - ReducedTransferConfig { - spi_clk: self.spi_clk, - mode: self.mode, - sod: self.sod, - blockmode: self.blockmode, - hw_cs: HwCs::CS_ID, - } + pub fn downgrade(self) -> CommonTransferConfig { + self.cfg } } -impl GenericTransferConfig for TransferConfig { +impl TransferConfigProvider for TransferConfigWithHwcs { /// Slave Output Disable fn sod(&mut self, sod: bool) { - self.sod = sod; + self.cfg.sod = sod; } fn blockmode(&mut self, blockmode: bool) { - self.blockmode = blockmode; + self.cfg.blockmode = blockmode; } fn mode(&mut self, mode: Mode) { - self.mode = mode; + self.cfg.mode = Some(mode); } - fn frequency(&mut self, spi_clk: Hertz) { - self.spi_clk = spi_clk; + fn clk_cfg(&mut self, clk_cfg: SpiClkConfig) { + self.cfg.clk_cfg = Some(clk_cfg); } fn hw_cs_id(&self) -> u8 { @@ -341,26 +352,40 @@ impl GenericTransferConfig for TransferConfig { } } -#[derive(Default)] /// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details pub struct SpiConfig { - /// Serial clock rate divider. Together with the CLKPRESCALE register, it determines - /// the SPI clock rate in master mode. 0 by default. Specifying a higher value - /// limits the maximum attainable SPI speed - pub scrdv: u8, + clk: SpiClkConfig, /// By default, configure SPI for master mode (ms == false) ms: bool, /// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control - sod: bool, + pub slave_output_disable: bool, /// Loopback mode. If you use this, don't connect MISO to MOSI, they will be tied internally - lbm: bool, + pub loopback_mode: bool, /// Enable Master Delayer Capture Mode. See Programmers Guide p.92 for more details - pub mdlycap: bool, + pub master_delayer_capture: bool, +} + +impl Default for SpiConfig { + fn default() -> Self { + Self { + // Default value is definitely valid. + clk: SpiClkConfig::from_div(DEFAULT_CLK_DIV).unwrap(), + ms: Default::default(), + slave_output_disable: Default::default(), + loopback_mode: Default::default(), + master_delayer_capture: Default::default(), + } + } } impl SpiConfig { pub fn loopback(mut self, enable: bool) -> Self { - self.lbm = enable; + self.loopback_mode = enable; + self + } + + pub fn clk_cfg(mut self, clk_cfg: SpiClkConfig) -> Self { + self.clk = clk_cfg; self } @@ -370,7 +395,7 @@ impl SpiConfig { } pub fn slave_output_disable(mut self, sod: bool) -> Self { - self.sod = sod; + self.slave_output_disable = sod; self } } @@ -419,6 +444,119 @@ pub struct Spi { pins: Pins, } +pub fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) { + match mode { + embedded_hal::spi::MODE_0 => (false, false), + embedded_hal::spi::MODE_1 => (false, true), + embedded_hal::spi::MODE_2 => (true, false), + embedded_hal::spi::MODE_3 => (true, true), + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SpiClkConfig { + prescale_val: u16, + scrdv: u8, +} + +impl SpiClkConfig { + pub fn prescale_val(&self) -> u16 { + self.prescale_val + } + pub fn scrdv(&self) -> u8 { + self.scrdv + } +} + +impl SpiClkConfig { + pub fn new(prescale_val: u16, scrdv: u8) -> Self { + Self { + prescale_val, + scrdv, + } + } + + pub fn from_div(div: u16) -> Result { + spi_clk_config_from_div(div) + } + + pub fn from_clk(sys_clk: impl Into, spi_clk: impl Into) -> Option { + clk_div_for_target_clock(sys_clk, spi_clk).map(|div| spi_clk_config_from_div(div).unwrap()) + } +} + +#[derive(Debug)] +pub enum SpiClkConfigError { + DivIsZero, + DivideValueNotEven, + ScrdvValueTooLarge, +} + +#[inline] +pub fn spi_clk_config_from_div(mut div: u16) -> Result { + if div == 0 { + return Err(SpiClkConfigError::DivIsZero); + } + if div % 2 != 0 { + return Err(SpiClkConfigError::DivideValueNotEven); + } + let mut prescale_val = 0; + + // find largest (even) prescale value that divides into div + for i in (2..=0xfe).rev().step_by(2) { + if div % i == 0 { + prescale_val = i; + break; + } + } + + if prescale_val == 0 { + return Err(SpiClkConfigError::DivideValueNotEven); + } + + div /= prescale_val; + if div > u8::MAX as u16 + 1 { + return Err(SpiClkConfigError::ScrdvValueTooLarge); + } + Ok(SpiClkConfig { + prescale_val, + scrdv: (div - 1) as u8, + }) +} + +#[inline] +pub fn clk_div_for_target_clock( + sys_clk: impl Into, + spi_clk: impl Into, +) -> Option { + let spi_clk = spi_clk.into(); + let sys_clk = sys_clk.into(); + if spi_clk > sys_clk { + return None; + } + + // Step 1: Calculate raw divider. + let raw_div = sys_clk.raw() / spi_clk.raw(); + let remainder = sys_clk.raw() % spi_clk.raw(); + + // Step 2: Round up if necessary. + let mut rounded_div = if remainder * 2 >= spi_clk.raw() { + raw_div + 1 + } else { + raw_div + }; + + if rounded_div % 2 != 0 { + // Take slower clock conservatively. + rounded_div += 1; + } + if rounded_div > u16::MAX as u32 { + return None; + } + Some(rounded_div as u16) +} + // Re-export this so it can be used for the constructor pub use crate::typelevel::NoneT; @@ -453,59 +591,56 @@ where spi: SpiI, pins: (Sck, Miso, Mosi), spi_cfg: SpiConfig, - transfer_cfg: Option<&ReducedTransferConfig>, + transfer_cfg: Option<&CommonTransferConfig>, ) -> Self { enable_peripheral_clock(syscfg, SpiI::PERIPH_SEL); let SpiConfig { - scrdv, + clk, ms, - sod, - lbm, - mdlycap, + slave_output_disable, + loopback_mode, + master_delayer_capture, } = spi_cfg; - let mut mode = MODE_0; - let mut clk_prescale = 0x02; + let mut init_mode = embedded_hal::spi::MODE_0; let mut ss = 0; let mut init_blockmode = false; if let Some(transfer_cfg) = transfer_cfg { - mode = transfer_cfg.mode; - clk_prescale = sys_clk.into().raw() / (transfer_cfg.spi_clk.raw() * (scrdv as u32 + 1)); + if let Some(mode) = transfer_cfg.mode { + init_mode = mode; + } if transfer_cfg.hw_cs != HwChipSelectId::Invalid { ss = transfer_cfg.hw_cs as u8; } init_blockmode = transfer_cfg.blockmode; } - let (cpo_bit, cph_bit) = match mode { - MODE_0 => (false, false), - MODE_1 => (false, true), - MODE_2 => (true, false), - MODE_3 => (true, true), - }; + let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(init_mode); spi.ctrl0().write(|w| { unsafe { w.size().bits(Word::word_reg()); - w.scrdv().bits(scrdv); + w.scrdv().bits(clk.scrdv); // Clear clock phase and polarity. Will be set to correct value for each // transfer w.spo().bit(cpo_bit); w.sph().bit(cph_bit) } }); + spi.ctrl1().write(|w| { - w.lbm().bit(lbm); - w.sod().bit(sod); + w.lbm().bit(loopback_mode); + w.sod().bit(slave_output_disable); w.ms().bit(ms); - w.mdlycap().bit(mdlycap); + w.mdlycap().bit(master_delayer_capture); w.blockmode().bit(init_blockmode); unsafe { w.ss().bits(ss) } }); + spi.clkprescale() + .write(|w| unsafe { w.bits(clk.prescale_val as u32) }); spi.fifo_clr().write(|w| { w.rxfifo().set_bit(); w.txfifo().set_bit() }); - spi.clkprescale().write(|w| unsafe { w.bits(clk_prescale) }); // Enable the peripheral as the last step as recommended in the // programmers guide spi.ctrl1().modify(|_, w| w.enable().set_bit()); @@ -522,33 +657,33 @@ where } } - #[inline] - pub fn cfg_clock(&mut self, spi_clk: impl Into) { - self.inner.cfg_clock(spi_clk); - } + delegate::delegate! { + to self.inner { + #[inline] + pub fn cfg_clock(&mut self, cfg: SpiClkConfig); - #[inline] - pub fn cfg_mode(&mut self, mode: Mode) { - self.inner.cfg_mode(mode); + #[inline] + pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError>; + + #[inline] + pub fn cfg_mode(&mut self, mode: Mode); + + #[inline] + pub fn perid(&self) -> u32; + + #[inline] + pub fn fill_word(&self) -> Word; + + pub fn cfg_transfer>( + &mut self, transfer_cfg: &TransferConfigWithHwcs + ); + } } pub fn set_fill_word(&mut self, fill_word: Word) { self.inner.fill_word = fill_word; } - pub fn fill_word(&self) -> Word { - self.inner.fill_word - } - - #[inline] - pub fn perid(&self) -> u32 { - self.inner.perid() - } - - pub fn cfg_transfer>(&mut self, transfer_cfg: &TransferConfig) { - self.inner.cfg_transfer(transfer_cfg); - } - /// Releases the SPI peripheral and associated pins pub fn release(self) -> (SpiI, (Sck, Miso, Mosi), SpiConfig) { (self.inner.spi, self.pins, self.inner.cfg) @@ -564,12 +699,20 @@ where >::Error: core::fmt::Debug, { #[inline] - pub fn cfg_clock(&mut self, spi_clk: impl Into) { - let clk_prescale = - self.sys_clk.raw() / (spi_clk.into().raw() * (self.cfg.scrdv as u32 + 1)); + pub fn cfg_clock(&mut self, cfg: SpiClkConfig) { + self.spi + .ctrl0() + .modify(|_, w| unsafe { w.scrdv().bits(cfg.scrdv) }); self.spi .clkprescale() - .write(|w| unsafe { w.bits(clk_prescale) }); + .write(|w| unsafe { w.bits(cfg.prescale_val as u32) }); + } + + #[inline] + pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError> { + let val = spi_clk_config_from_div(div)?; + self.cfg_clock(val); + Ok(()) } #[inline] @@ -586,6 +729,11 @@ where }); } + #[inline] + pub fn fill_word(&self) -> Word { + self.fill_word + } + #[inline] pub fn clear_tx_fifo(&self) { self.spi.fifo_clr().write(|w| w.txfifo().set_bit()); @@ -629,13 +777,17 @@ where pub fn cfg_transfer>( &mut self, - transfer_cfg: &TransferConfig, + transfer_cfg: &TransferConfigWithHwcs, ) { - self.cfg_clock(transfer_cfg.spi_clk); - self.cfg_mode(transfer_cfg.mode); - self.blockmode = transfer_cfg.blockmode; + if let Some(trans_clk_div) = transfer_cfg.cfg.clk_cfg { + self.cfg_clock(trans_clk_div); + } + if let Some(mode) = transfer_cfg.cfg.mode { + self.cfg_mode(mode); + } + self.blockmode = transfer_cfg.cfg.blockmode; self.spi.ctrl1().modify(|_, w| { - if transfer_cfg.sod { + if transfer_cfg.cfg.sod { w.sod().set_bit(); } else if transfer_cfg.hw_cs.is_some() { w.sod().clear_bit(); @@ -645,7 +797,7 @@ where } else { w.sod().clear_bit(); } - if transfer_cfg.blockmode { + if transfer_cfg.cfg.blockmode { w.blockmode().set_bit(); } else { w.blockmode().clear_bit(); @@ -682,34 +834,52 @@ where .unwrap() } + fn flush_internal(&self) { + let mut status_reg = self.spi.status().read(); + while status_reg.tfe().bit_is_clear() + || status_reg.rne().bit_is_set() + || status_reg.busy().bit_is_set() + { + if status_reg.rne().bit_is_set() { + self.read_single_word(); + } + status_reg = self.spi.status().read(); + } + } + fn transfer_preparation(&self, words: &[Word]) -> Result<(), Infallible> { if words.is_empty() { return Ok(()); } - let mut status_reg = self.spi.status().read(); - // Wait until all bytes have been transferred. - while status_reg.tfe().bit_is_clear() { - // Ignore all received read words. - if status_reg.rne().bit_is_set() { - self.clear_rx_fifo(); - } - status_reg = self.spi.status().read(); - } - // Ignore all received read words. - if status_reg.rne().bit_is_set() { - self.clear_rx_fifo(); - } + self.flush_internal(); Ok(()) } - fn initial_send_fifo_pumping(&self, words: Option<&[Word]>) -> usize { + // Returns the actual bytes sent. + fn initial_send_fifo_pumping_with_words(&self, words: &[Word]) -> usize { if self.blockmode { self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit()) } // Fill the first half of the write FIFO let mut current_write_idx = 0; - for _ in 0..core::cmp::min(FILL_DEPTH, words.map_or(0, |words| words.len())) { - self.send_blocking(words.map_or(self.fill_word, |words| words[current_write_idx])); + for _ in 0..core::cmp::min(FILL_DEPTH, words.len()) { + self.send_blocking(words[current_write_idx]); + current_write_idx += 1; + } + if self.blockmode { + self.spi.ctrl1().modify(|_, w| w.mtxpause().clear_bit()) + } + current_write_idx + } + + fn initial_send_fifo_pumping_with_fill_words(&self, send_len: usize) -> usize { + if self.blockmode { + self.spi.ctrl1().modify(|_, w| w.mtxpause().set_bit()) + } + // Fill the first half of the write FIFO + let mut current_write_idx = 0; + for _ in 0..core::cmp::min(FILL_DEPTH, send_len) { + self.send_blocking(self.fill_word); current_write_idx += 1; } if self.blockmode { @@ -778,7 +948,7 @@ where fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { self.transfer_preparation(words)?; let mut current_read_idx = 0; - let mut current_write_idx = self.initial_send_fifo_pumping(None); + let mut current_write_idx = self.initial_send_fifo_pumping_with_fill_words(words.len()); loop { if current_write_idx < words.len() { self.send_blocking(self.fill_word); @@ -797,7 +967,7 @@ where fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { self.transfer_preparation(words)?; - let mut current_write_idx = self.initial_send_fifo_pumping(Some(words)); + let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words); while current_write_idx < words.len() { self.send_blocking(words[current_write_idx]); current_write_idx += 1; @@ -812,7 +982,7 @@ where fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { self.transfer_preparation(write)?; let mut current_read_idx = 0; - let mut current_write_idx = self.initial_send_fifo_pumping(Some(write)); + let mut current_write_idx = self.initial_send_fifo_pumping_with_words(write); while current_read_idx < read.len() || current_write_idx < write.len() { if current_write_idx < write.len() { self.send_blocking(write[current_write_idx]); @@ -830,7 +1000,7 @@ where fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { self.transfer_preparation(words)?; let mut current_read_idx = 0; - let mut current_write_idx = self.initial_send_fifo_pumping(Some(words)); + let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words); while current_read_idx < words.len() || current_write_idx < words.len() { if current_write_idx < words.len() { @@ -846,12 +1016,7 @@ where } fn flush(&mut self) -> Result<(), Self::Error> { - let status_reg = self.spi.status().read(); - while status_reg.tfe().bit_is_clear() || status_reg.rne().bit_is_set() { - if status_reg.rne().bit_is_set() { - self.read_single_word(); - } - } + self.flush_internal(); Ok(()) } } @@ -877,23 +1042,13 @@ impl< where >::Error: core::fmt::Debug, { - fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { - self.inner.read(words) - } - - fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { - self.inner.write(words) - } - - fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { - self.inner.transfer(read, write) - } - - fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { - self.inner.transfer_in_place(words) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - self.inner.flush() + delegate::delegate! { + to self.inner { + fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error>; + fn write(&mut self, words: &[Word]) -> Result<(), Self::Error>; + fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error>; + fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error>; + fn flush(&mut self) -> Result<(), Self::Error>; + } } } diff --git a/vorago-reb1/Cargo.toml b/vorago-reb1/Cargo.toml index 5512fde..86c2f65 100644 --- a/vorago-reb1/Cargo.toml +++ b/vorago-reb1/Cargo.toml @@ -14,12 +14,15 @@ categories = ["aerospace", "embedded", "no-std", "hardware-support"] cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m-rt = "0.7" embedded-hal = "1" +nb = "1" +bitfield = "0.17" [dependencies.max116xx-10bit] version = "0.3" [dependencies.va108xx-hal] -version = "0.7" +version = ">=0.7, <0.8" +path = "../va108xx-hal" features = ["rt"] [features] @@ -28,20 +31,8 @@ rt = ["va108xx-hal/rt"] [dev-dependencies] panic-halt = "0.2" nb = "1" - -[dev-dependencies.rtt-target] -version = "0.5" - -[dev-dependencies.panic-rtt-target] -version = "0.1" - -[dev-dependencies.rtic] -version = "2" -features = ["thumbv6-backend"] - -[dev-dependencies.rtic-monotonics] -version = "1" -features = ["cortex-m-systick"] +rtt-target = "0.5" +panic-rtt-target = "0.1" [package.metadata.docs.rs] all-features = true diff --git a/vorago-reb1/examples/adxl343-accelerometer.rs b/vorago-reb1/examples/adxl343-accelerometer.rs index 483d4dc..0b67654 100644 --- a/vorago-reb1/examples/adxl343-accelerometer.rs +++ b/vorago-reb1/examples/adxl343-accelerometer.rs @@ -9,11 +9,12 @@ use embedded_hal::spi::SpiBus; use embedded_hal::{delay::DelayNs, digital::OutputPin}; use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; +use va108xx_hal::spi::SpiClkConfig; use va108xx_hal::{ gpio::PinsA, pac, prelude::*, - spi::{Spi, SpiConfig, TransferConfig}, + spi::{Spi, SpiConfig, TransferConfigWithHwcs}, timer::set_up_ms_delay_provider, }; @@ -45,9 +46,9 @@ fn main() -> ! { .set_high() .expect("Setting ADC chip select high failed"); - let transfer_cfg = TransferConfig::new( - 1.MHz(), - embedded_hal::spi::MODE_3, + let transfer_cfg = TransferConfigWithHwcs::new( + Some(SpiClkConfig::from_clk(50.MHz(), 1.MHz()).expect("creating SPI clock config failed")), + Some(embedded_hal::spi::MODE_3), Some(cs_pin), false, true, diff --git a/vorago-reb1/examples/max11619-adc.rs b/vorago-reb1/examples/max11619-adc.rs index 8b76259..84c34de 100644 --- a/vorago-reb1/examples/max11619-adc.rs +++ b/vorago-reb1/examples/max11619-adc.rs @@ -15,13 +15,13 @@ use max116xx_10bit::VoltageRefMode; use max116xx_10bit::{AveragingConversions, AveragingResults}; use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; -use va108xx_hal::spi::{NoneT, OptionalHwCs}; +use va108xx_hal::spi::{OptionalHwCs, SpiClkConfig}; use va108xx_hal::timer::CountDownTimer; use va108xx_hal::{ gpio::PinsA, pac::{self, interrupt}, prelude::*, - spi::{Spi, SpiBase, SpiConfig, TransferConfig}, + spi::{Spi, SpiBase, SpiConfig, TransferConfigWithHwcs}, timer::{default_ms_irq_handler, set_up_ms_tick, DelayMs, IrqCfg}, }; use va108xx_hal::{port_mux, FunSel, PortSel}; @@ -103,6 +103,8 @@ impl> SpiDevice for SpiWithHwCs ! { rtt_init_print!(); @@ -113,7 +115,7 @@ fn main() -> ! { IrqCfg::new(pac::Interrupt::OC0, true, true), &mut dp.sysconfig, Some(&mut dp.irqsel), - 50.MHz(), + SYS_CLK, dp.tim0, ); let delay = DelayMs::new(tim0).unwrap(); @@ -141,7 +143,12 @@ fn main() -> ! { .set_high() .expect("Setting accelerometer chip select high failed"); - let transfer_cfg = TransferConfig::::new(3.MHz(), spi::MODE_0, None, true, false); + let transfer_cfg = TransferConfigWithHwcs::new_no_hw_cs( + Some(SpiClkConfig::from_clk(SYS_CLK, 3.MHz()).unwrap()), + Some(spi::MODE_0), + true, + false, + ); let spi = Spi::new( &mut dp.sysconfig, 50.MHz(), diff --git a/vorago-reb1/src/lib.rs b/vorago-reb1/src/lib.rs index bc44aae..1ccd544 100644 --- a/vorago-reb1/src/lib.rs +++ b/vorago-reb1/src/lib.rs @@ -3,5 +3,6 @@ pub mod button; pub mod leds; +pub mod m95m01; pub mod max11619; pub mod temp_sensor; diff --git a/vorago-reb1/src/m95m01.rs b/vorago-reb1/src/m95m01.rs new file mode 100644 index 0000000..9ca8260 --- /dev/null +++ b/vorago-reb1/src/m95m01.rs @@ -0,0 +1,138 @@ +use embedded_hal::spi::SpiDevice; + +bitfield::bitfield! { + pub struct StatusReg(u8); + impl Debug; + u8; + status_register_write_protect, _: 7, 0; + block_protection_bits, set_block_protection_bits: 3, 2; + write_enable_latch, _: 1; + write_in_progress, _: 0; +} + +// Registers. +pub mod regs { + /// Write status register command. + pub const WRSR: u8 = 0x01; + // Write command. + pub const WRITE: u8 = 0x02; + // Read command. + pub const READ: u8 = 0x03; + /// Write disable command. + pub const WRDI: u8 = 0x04; + /// Read status register command. + pub const RDSR: u8 = 0x05; + /// Write enable command. + pub const WREN: u8 = 0x06; +} + +use regs::*; + +/// Driver for the ST device M95M01 EEPROM memory. +pub struct M95M01 { + spi: Spi, +} + +pub enum Error { + Spi(SpiError), + BufTooShort, +} + +impl From for Error { + fn from(value: SpiError) -> Self { + Self::Spi(value) + } +} + +impl M95M01 { + pub fn new(spi: Spi) -> Result { + let mut spi_dev = Self { spi }; + spi_dev.clear_block_protection()?; + Ok(spi_dev) + } + + pub fn release(mut self) -> Result { + self.set_block_protection()?; + Ok(self.spi) + } + + // Wait until the write-in-progress state is cleared. This exposes a [nb] API, so this function + // will return [nb::Error::WouldBlock] if the EEPROM is still busy. + pub fn writes_are_done(&mut self) -> nb::Result<(), Spi::Error> { + let mut read: [u8; 2] = [0; 2]; + self.spi.transfer(&mut read, &[regs::RDSR, 0x00])?; + let rdsr = StatusReg(read[1]); + if rdsr.write_in_progress() { + return Err(nb::Error::WouldBlock); + } + Ok(()) + } + + pub fn write_enable(&mut self) -> Result<(), Spi::Error> { + self.spi.write(&[regs::WREN]) + } + + pub fn clear_block_protection(&mut self) -> Result<(), Spi::Error> { + self.spi.write(&[WREN, WRSR, 0x00]) + } + + pub fn set_block_protection(&mut self) -> Result<(), Spi::Error> { + let mut reg = StatusReg(0); + reg.set_block_protection_bits(0b11); + self.spi.write(&[WREN, WRSR, reg.0]) + } + + pub fn write(&mut self, address: u32, data: &[u8]) -> Result<(), Spi::Error> { + self.write_enable()?; + self.spi.write(&[ + WRITE, + ((address >> 16) & 0xff) as u8, + ((address >> 8) & 0xff) as u8, + (address & 0xff) as u8, + ])?; + self.spi.write(data)?; + Ok(()) + } + + pub fn read( + &mut self, + address: u32, + size: usize, + buf: &mut [u8], + ) -> Result<(), Error> { + if buf.len() < size { + return Err(Error::BufTooShort); + } + self.spi.write(&[ + READ, + ((address >> 16) & 0xff) as u8, + ((address >> 8) & 0xff) as u8, + (address & 0xff) as u8, + ])?; + self.spi.read(&mut buf[0..size])?; + Ok(()) + } + + pub fn verify(&mut self, address: u32, data: &[u8]) -> Result { + // Write the read command and address + self.spi.write(&[ + READ, + ((address >> 16) & 0xff) as u8, + ((address >> 8) & 0xff) as u8, + (address & 0xff) as u8, + ])?; + + // Read and compare each byte in place + for original_byte in data.iter() { + let mut read_byte = [0u8]; + self.spi.read(&mut read_byte)?; + + // Compare read byte with original + if read_byte[0] != *original_byte { + return Ok(false); + } + } + + Ok(true) + } +}