use crate::peripherals::Efuse;
const EFUSE_READ_MAGIC: u32 = 0x5A5A;
const EFUSE_WRITE_MAGIC: u32 = 0xA5A5;
pub const EFUSE_MAX_BYTES: u16 = 256;
const EFUSE_PROGRAM_DELAY_US: u32 = 100;
#[inline]
const fn word_index(byte_addr: u16) -> usize {
(byte_addr / 2) as usize
}
#[inline]
const fn extract_byte(word: u32, byte_addr: u16) -> u8 {
if byte_addr & 1 != 0 { ((word >> 8) & 0xFF) as u8 } else { (word & 0xFF) as u8 }
}
#[inline]
const fn pack_byte(value: u8, byte_addr: u16) -> u32 {
if byte_addr & 1 != 0 { (value as u32) << 8 } else { value as u32 }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum EfuseError {
OutOfRange,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct EfuseByteAddress(u16);
impl EfuseByteAddress {
pub const fn from_byte(byte_addr: u16) -> Option<Self> {
if byte_addr < EFUSE_MAX_BYTES { Some(Self(byte_addr)) } else { None }
}
pub const fn byte(self) -> u16 {
self.0
}
}
pub struct EfuseDriver<'d> {
_efuse: Efuse<'d>,
}
impl<'d> EfuseDriver<'d> {
pub fn new(efuse: Efuse<'d>) -> Self {
let mut driver = Self { _efuse: efuse };
driver.set_clock_period_raw(default_clock_period());
driver
}
#[cfg(feature = "unstable")]
pub(crate) fn into_inner(self) -> Efuse<'d> {
self._efuse
}
fn regs() -> &'static crate::soc::pac::efuse::RegisterBlock {
unsafe { &*Efuse::ptr() }
}
fn set_clock_period_raw(&mut self, period: u8) {
unsafe {
Self::regs().efuse_clk_period().write(|w| w.bits(period as u32));
}
}
#[instability::unstable]
pub fn set_clock_period(&mut self, period: u8) {
self.set_clock_period_raw(period);
}
#[instability::unstable]
pub fn status(&self) -> EfuseStatus {
let sts = Self::regs().efuse_sts().read();
EfuseStatus {
man_status: sts.man_sts().bits(),
boot0_done: sts.boot0_done().bit_is_set(),
boot1_done: sts.boot1_done().bit_is_set(),
boot2_done: sts.boot2_done().bit_is_set(),
}
}
pub fn read_byte(&mut self, byte_addr: EfuseByteAddress) -> u8 {
unsafe { Self::read_byte_unchecked(byte_addr) }
}
#[instability::unstable]
pub unsafe fn read_byte_unchecked(byte_addr: EfuseByteAddress) -> u8 {
critical_section::with(|_| {
let byte_addr = byte_addr.byte();
unsafe {
Self::regs().efuse_ctl_data().write(|w| w.bits(EFUSE_READ_MAGIC));
}
let word = Self::regs().efuse_data(word_index(byte_addr)).read().bits();
extract_byte(word, byte_addr)
})
}
#[instability::unstable]
pub fn read_buffer(&mut self, start_byte: EfuseByteAddress, buf: &mut [u8]) -> Result<(), EfuseError> {
let start_byte = start_byte.byte();
for (i, slot) in buf.iter_mut().enumerate() {
if i > u16::MAX as usize {
return Err(EfuseError::OutOfRange);
}
let addr =
start_byte.checked_add(i as u16).and_then(EfuseByteAddress::from_byte).ok_or(EfuseError::OutOfRange)?;
*slot = self.read_byte(addr);
}
Ok(())
}
#[instability::unstable]
pub fn write_byte(&mut self, byte_addr: EfuseByteAddress, value: u8) {
let byte_addr = byte_addr.byte();
let delay = crate::delay::Delay::new();
unsafe {
Self::regs().efuse_ctl_data().write(|w| w.bits(EFUSE_WRITE_MAGIC));
Self::regs().efuse_avdd_ctl().write(|w| w.bits(1));
}
delay.delay_micros(EFUSE_PROGRAM_DELAY_US);
unsafe {
Self::regs().efuse_data(word_index(byte_addr)).write(|w| w.bits(pack_byte(value, byte_addr)));
Self::regs().efuse_avdd_ctl().write(|w| w.bits(0));
}
delay.delay_micros(EFUSE_PROGRAM_DELAY_US);
}
}
fn default_clock_period() -> u8 {
if crate::soc::chip::uart_boot_clock_hz() == crate::soc::chip::UART_BOOT_CLOCK_24M_HZ { 0x29 } else { 0x19 }
}
#[derive(Debug, Clone, Copy)]
#[instability::unstable]
pub struct EfuseStatus {
pub man_status: u8,
pub boot0_done: bool,
pub boot1_done: bool,
pub boot2_done: bool,
}
#[allow(dead_code)]
impl EfuseStatus {
pub fn boot_complete(&self) -> bool {
self.boot0_done && self.boot1_done && self.boot2_done
}
}
#[cfg(all(test, not(target_arch = "riscv32")))]
mod tests {
use super::*;
#[test]
fn test_magic_values() {
assert_eq!(EFUSE_READ_MAGIC, 0x5A5A);
assert_eq!(EFUSE_WRITE_MAGIC, 0xA5A5);
}
#[test]
fn test_word_index() {
assert_eq!(word_index(0), 0);
assert_eq!(word_index(1), 0);
assert_eq!(word_index(2), 1);
assert_eq!(word_index(3), 1);
assert_eq!(word_index(255), 127);
}
#[test]
fn test_extract_byte_even_odd() {
let word = 0xABCD;
assert_eq!(extract_byte(word, 0), 0xCD); assert_eq!(extract_byte(word, 1), 0xAB); assert_eq!(extract_byte(word, 2), 0xCD); }
#[test]
fn test_pack_byte_even_odd() {
assert_eq!(pack_byte(0xCD, 0), 0x00CD); assert_eq!(pack_byte(0xAB, 1), 0xAB00); }
#[test]
fn test_pack_extract_roundtrip() {
for addr in [0u16, 1, 42, 255] {
for v in [0u8, 1, 0x5A, 0xFF] {
assert_eq!(extract_byte(pack_byte(v, addr), addr), v);
}
}
}
#[test]
fn byte_address_accepts_only_array_range() {
assert_eq!(EfuseByteAddress::from_byte(0).unwrap().byte(), 0);
assert_eq!(EfuseByteAddress::from_byte(EFUSE_MAX_BYTES - 1).unwrap().byte(), EFUSE_MAX_BYTES - 1);
assert_eq!(EfuseByteAddress::from_byte(EFUSE_MAX_BYTES), None);
assert_eq!(EfuseByteAddress::from_byte(u16::MAX), None);
}
#[test]
fn test_efuse_boot_status_complete() {
let sts = EfuseStatus { man_status: 0, boot0_done: true, boot1_done: true, boot2_done: true };
assert!(sts.boot_complete());
let partial = EfuseStatus { man_status: 0, boot0_done: true, boot1_done: false, boot2_done: true };
assert!(!partial.boot_complete());
}
}
#[cfg(all(test, not(target_arch = "riscv32")))]
mod proptests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn word_index_in_range(addr in 0u16..EFUSE_MAX_BYTES) {
let idx = word_index(addr);
prop_assert_eq!(idx, (addr / 2) as usize);
prop_assert!(idx < 128);
}
#[test]
fn pack_extract_roundtrip(addr in any::<u16>(), v in any::<u8>()) {
prop_assert_eq!(extract_byte(pack_byte(v, addr), addr), v);
}
#[test]
fn extract_byte_lane(word in any::<u32>(), addr in any::<u16>()) {
let b = extract_byte(word, addr);
let expected = if addr & 1 != 0 { ((word >> 8) & 0xFF) as u8 } else { (word & 0xFF) as u8 };
prop_assert_eq!(b, expected);
}
}
}