Skip to main content

ws63_hal/
efuse.rs

1//! eFuse (OTP) driver for WS63 — v151 controller.
2//!
3//! Register map and access sequence are cross-checked against the WS63 C SDK
4//! (`hal_efuse_v151.c`, `hal_efuse_v151_reg_op.h`, `efuse_porting.c`):
5//!
6//! - **Status** `EFUSE_STS` at base+0x2C (boot-done flags, read-only).
7//! - **Control block** at base+0x30: `EFUSE_CTL_DATA` (mode select), +0x34
8//!   `EFUSE_CLK_PERIOD`, +0x3C `EFUSE_AVDD_CTL` (program-voltage switch).
9//! - **Data window** at base+0x800: 128 × 32-bit words, each packing two eFuse
10//!   bytes (even byte address in `[7:0]`, odd in `[15:8]`). Word index =
11//!   `byte_addr / 2`.
12//!
13//! An access is *armed* by writing a 16-bit magic to `EFUSE_CTL_DATA`:
14//! `0x5A5A` = read mode, `0xA5A5` = program mode (`HAL_EFUSE_READ_MODE` /
15//! `HAL_EFUSE_WRITE_MODE`). A read then loads the latched word from the window;
16//! a program raises AVDD, writes the byte, and lowers AVDD with timing delays.
17//!
18//! # Safety / status
19//!
20//! This driver has **not been validated on silicon**. Programming an eFuse is a
21//! one-time, irreversible operation; [`EfuseDriver::write_byte`] is provided for
22//! completeness but should be treated as experimental.
23
24use crate::peripherals::Efuse;
25
26/// Magic written to `EFUSE_CTL_DATA` to arm a read (`HAL_EFUSE_READ_MODE`).
27const EFUSE_READ_MAGIC: u32 = 0x5A5A;
28/// Magic written to `EFUSE_CTL_DATA` to arm a program (`HAL_EFUSE_WRITE_MODE`).
29const EFUSE_WRITE_MAGIC: u32 = 0xA5A5;
30/// eFuse array size for one region: 2048 bits = 256 bytes
31/// (`EFUSE_REGION_MAX_BITS` in the SDK).
32pub const EFUSE_MAX_BYTES: u16 = 256;
33/// Settle delay around a program pulse (`HAL_EFUSE_DELAY_US`).
34const EFUSE_PROGRAM_DELAY_US: u32 = 100;
35
36/// Word index into the data window for a given eFuse byte address.
37///
38/// Each 32-bit window word holds two consecutive eFuse bytes, so the word
39/// index is `byte_addr / 2` (the SDK computes `(offset >> 1) << 2` as a byte
40/// offset, i.e. `word_index * 4`).
41#[inline]
42const fn word_index(byte_addr: u16) -> usize {
43    (byte_addr / 2) as usize
44}
45
46/// Extract one eFuse byte from a window word: even address → low byte
47/// (`[7:0]`), odd address → high byte (`[15:8]`).
48#[inline]
49const fn extract_byte(word: u32, byte_addr: u16) -> u8 {
50    if byte_addr & 1 != 0 { ((word >> 8) & 0xFF) as u8 } else { (word & 0xFF) as u8 }
51}
52
53/// Pack one eFuse byte into the window word for programming: even address →
54/// low byte, odd address → high byte (matches `hal_efuse_write_operation`).
55#[inline]
56const fn pack_byte(value: u8, byte_addr: u16) -> u32 {
57    if byte_addr & 1 != 0 { (value as u32) << 8 } else { value as u32 }
58}
59
60/// eFuse access error.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum EfuseError {
63    /// Byte address is outside the eFuse array (`>= EFUSE_MAX_BYTES`).
64    OutOfRange,
65}
66
67/// eFuse controller driver.
68pub struct EfuseDriver<'d> {
69    _efuse: Efuse<'d>,
70}
71
72impl<'d> EfuseDriver<'d> {
73    /// Create a new eFuse driver.
74    pub fn new(efuse: Efuse<'d>) -> Self {
75        Self { _efuse: efuse }
76    }
77
78    fn regs(&self) -> &'static ws63_pac::efuse::RegisterBlock {
79        // SAFETY: PAC peripheral pointer is a static physical MMIO address, always valid.
80        unsafe { &*Efuse::ptr() }
81    }
82
83    /// Set the eFuse clock period (cycles). The SDK uses `0x29` @ 24 MHz TCXO
84    /// and `0x19` @ 40 MHz; call before any read/program.
85    pub fn set_clock_period(&mut self, period: u8) {
86        unsafe {
87            self.regs().efuse_clk_period().write(|w| w.bits(period as u32));
88        }
89    }
90
91    /// Read the boot-done status register.
92    pub fn status(&self) -> EfuseStatus {
93        let sts = self.regs().efuse_sts().read();
94        EfuseStatus {
95            man_status: sts.man_sts().bits(),
96            boot0_done: sts.boot0_done().bit_is_set(),
97            boot1_done: sts.boot1_done().bit_is_set(),
98            boot2_done: sts.boot2_done().bit_is_set(),
99        }
100    }
101
102    /// Read a single eFuse byte at `byte_addr`.
103    ///
104    /// Arms read mode (`0x5A5A`), then loads the latched word from the data
105    /// window and extracts the requested byte. No delay is required for reads
106    /// (matches `hal_efuse_read_byte`).
107    pub fn read_byte(&mut self, byte_addr: u16) -> Result<u8, EfuseError> {
108        if byte_addr >= EFUSE_MAX_BYTES {
109            return Err(EfuseError::OutOfRange);
110        }
111        unsafe {
112            self.regs().efuse_ctl_data().write(|w| w.bits(EFUSE_READ_MAGIC));
113        }
114        let word = self.regs().efuse_data(word_index(byte_addr)).read().bits();
115        Ok(extract_byte(word, byte_addr))
116    }
117
118    /// Read `buf.len()` consecutive eFuse bytes starting at `start_byte`.
119    pub fn read_buffer(&mut self, start_byte: u16, buf: &mut [u8]) -> Result<(), EfuseError> {
120        for (i, slot) in buf.iter_mut().enumerate() {
121            *slot = self.read_byte(start_byte + i as u16)?;
122        }
123        Ok(())
124    }
125
126    /// Program a single eFuse byte (**one-time, irreversible**).
127    ///
128    /// Sequence (per `hal_efuse_write_operation`): arm write mode (`0xA5A5`),
129    /// raise AVDD, settle, write the packed byte to the window, lower AVDD,
130    /// settle. eFuse bits can only be burned 0→1; this does not erase.
131    ///
132    /// Not validated on silicon — treat as experimental.
133    pub fn write_byte(&mut self, byte_addr: u16, value: u8) -> Result<(), EfuseError> {
134        if byte_addr >= EFUSE_MAX_BYTES {
135            return Err(EfuseError::OutOfRange);
136        }
137        let delay = crate::delay::Delay::new();
138        unsafe {
139            self.regs().efuse_ctl_data().write(|w| w.bits(EFUSE_WRITE_MAGIC));
140            self.regs().efuse_avdd_ctl().write(|w| w.bits(1));
141        }
142        delay.delay_micros(EFUSE_PROGRAM_DELAY_US);
143        unsafe {
144            self.regs().efuse_data(word_index(byte_addr)).write(|w| w.bits(pack_byte(value, byte_addr)));
145            self.regs().efuse_avdd_ctl().write(|w| w.bits(0));
146        }
147        delay.delay_micros(EFUSE_PROGRAM_DELAY_US);
148        Ok(())
149    }
150}
151
152/// eFuse status information.
153#[derive(Debug, Clone, Copy)]
154pub struct EfuseStatus {
155    /// Manufacturing status (2-bit field).
156    pub man_status: u8,
157    /// Boot stage 0 completed.
158    pub boot0_done: bool,
159    /// Boot stage 1 completed.
160    pub boot1_done: bool,
161    /// Boot stage 2 completed.
162    pub boot2_done: bool,
163}
164
165impl EfuseStatus {
166    /// Returns true if all boot stages completed successfully.
167    pub fn boot_complete(&self) -> bool {
168        self.boot0_done && self.boot1_done && self.boot2_done
169    }
170}
171
172// ── Tests ──────────────────────────────────────────────────────
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn test_magic_values() {
180        // Mode-select magics must match the SDK exactly.
181        assert_eq!(EFUSE_READ_MAGIC, 0x5A5A);
182        assert_eq!(EFUSE_WRITE_MAGIC, 0xA5A5);
183    }
184
185    #[test]
186    fn test_word_index() {
187        // Two bytes per 32-bit window word.
188        assert_eq!(word_index(0), 0);
189        assert_eq!(word_index(1), 0);
190        assert_eq!(word_index(2), 1);
191        assert_eq!(word_index(3), 1);
192        assert_eq!(word_index(255), 127);
193    }
194
195    #[test]
196    fn test_extract_byte_even_odd() {
197        let word = 0xABCD;
198        assert_eq!(extract_byte(word, 0), 0xCD); // even → low byte
199        assert_eq!(extract_byte(word, 1), 0xAB); // odd  → high byte
200        assert_eq!(extract_byte(word, 2), 0xCD); // even (word index differs)
201    }
202
203    #[test]
204    fn test_pack_byte_even_odd() {
205        assert_eq!(pack_byte(0xCD, 0), 0x00CD); // even → low byte
206        assert_eq!(pack_byte(0xAB, 1), 0xAB00); // odd  → high byte
207    }
208
209    #[test]
210    fn test_pack_extract_roundtrip() {
211        // Packing then extracting the same byte address must recover the value.
212        for addr in [0u16, 1, 42, 255] {
213            for v in [0u8, 1, 0x5A, 0xFF] {
214                assert_eq!(extract_byte(pack_byte(v, addr), addr), v);
215            }
216        }
217    }
218
219    #[test]
220    fn test_efuse_boot_status_complete() {
221        let sts = EfuseStatus { man_status: 0, boot0_done: true, boot1_done: true, boot2_done: true };
222        assert!(sts.boot_complete());
223
224        let partial = EfuseStatus { man_status: 0, boot0_done: true, boot1_done: false, boot2_done: true };
225        assert!(!partial.boot_complete());
226    }
227}
228
229// ── Property-based fuzz tests ──────────────────────────────────
230
231#[cfg(test)]
232mod proptests {
233    use super::*;
234    use proptest::prelude::*;
235
236    proptest! {
237        /// Fuzz: word index is always byte_addr/2 and within the 128-word window.
238        #[test]
239        fn word_index_in_range(addr in 0u16..EFUSE_MAX_BYTES) {
240            let idx = word_index(addr);
241            prop_assert_eq!(idx, (addr / 2) as usize);
242            prop_assert!(idx < 128);
243        }
244
245        /// Fuzz: pack/extract is a faithful round-trip for any byte and address.
246        #[test]
247        fn pack_extract_roundtrip(addr in any::<u16>(), v in any::<u8>()) {
248            prop_assert_eq!(extract_byte(pack_byte(v, addr), addr), v);
249        }
250
251        /// Fuzz: extract never reads beyond the addressed byte lane.
252        #[test]
253        fn extract_byte_lane(word in any::<u32>(), addr in any::<u16>()) {
254            let b = extract_byte(word, addr);
255            let expected = if addr & 1 != 0 { ((word >> 8) & 0xFF) as u8 } else { (word & 0xFF) as u8 };
256            prop_assert_eq!(b, expected);
257        }
258    }
259}