Skip to main content

imxrt_hal/chip/drivers/
ocotp.rs

1//! On-Chip One-Time Programmable Controller
2//!
3//! The OCOTP driver lets you read and write fuses, from firmware.
4//! The driver is available for most chip-specific builds,
5//! and it maintains the same baseline API. However, only
6//! 11xx MCUs may signal SEC and DED events.
7//!
8//! The driver does not manage any timing registers in the
9//! OCOTP address space. You may need to do this yourself,
10//! depending on your chip and clock settings.
11//!
12//! # Caution: Writes are Permanent
13//!
14//! *A fuse write is irreversible. The bits set high cannot be cleared. Furthermore,
15//! if the fuse is backed by ECC redundancy or is locked by the write, you cannot
16//! change nearby bits in the same fuse bank or word.*
17
18use crate::ral::{self, ocotp};
19use core::task::{self, Poll};
20
21/// The magic number to perform a fuse write.
22const WRITE_UNLOCK: u32 = 0x3E77;
23
24/// Possible errors when accessing fuses.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26#[cfg_attr(feature = "defmt", derive(defmt::Format))]
27pub enum Error {
28    /// Access to a locked region detected.
29    LockedRegionAccess,
30    /// Double error detected in fuse value during read.
31    DedRead,
32    /// Failed to program a fuse during a write.
33    Programming,
34}
35
36/// The OCOTP driver.
37pub struct Ocotp {
38    pub(crate) ocotp: ocotp::OCOTP,
39}
40
41impl Ocotp {
42    /// Construct the driver interface, given the peripheral instance.
43    pub fn new(ocotp: ocotp::OCOTP) -> Self {
44        Self { ocotp }
45    }
46
47    /// Check common status bits.
48    fn check_status(&mut self) -> Poll<Result<(), Error>> {
49        let (busy, error) = ral::read_reg!(ocotp, self.ocotp, CTRL, BUSY, ERROR);
50        if error != 0 {
51            ral::write_reg!(ocotp, self.ocotp, CTRL_SET, ERROR: 1);
52            return Poll::Ready(Err(Error::LockedRegionAccess));
53        }
54        if busy != 0 {
55            return Poll::Pending;
56        }
57
58        Poll::Ready(Ok(()))
59    }
60
61    /// Begin to read a fuse.
62    ///
63    /// Returns `Poll::Pending` if there's an in-flight OCOTP action already
64    /// in progress. If this returns `Poll::Ready(Ok(()))`, then a read operation
65    /// is in flight.
66    ///
67    /// If this returns an error, the error is a lagging error from a prior operation.
68    /// The implementation will automatically clear that error.
69    fn start_fuse_read(&mut self, fuse_address: FuseAddress) -> Poll<Result<(), Error>> {
70        task::ready!(self.check_status())?;
71
72        ral::modify_reg!(ocotp, self.ocotp, CTRL, ADDR: u32::from(fuse_address.0));
73        ral::write_reg!(ocotp, self.ocotp, READ_CTRL, READ_FUSE: 1);
74        Poll::Ready(Ok(()))
75    }
76
77    /// Begin to write data to a fuse.
78    ///
79    /// Returns `Poll::Pending` if there's an in-flight OCOTP action already in
80    /// progress. If this returns `Poll::Ready(Ok(()))`, then the write operation
81    /// is in flight.
82    ///
83    /// If this returns an error, the error is a lagging error from a prior operation.
84    /// The implementation will automatically clear that error.
85    fn start_fuse_write(
86        &mut self,
87        fuse_address: FuseAddress,
88        fuse_value: u32,
89    ) -> Poll<Result<(), Error>> {
90        task::ready!(self.check_status())?;
91
92        ral::modify_reg!(ocotp, self.ocotp, CTRL, ADDR: u32::from(fuse_address.0), WR_UNLOCK: WRITE_UNLOCK);
93        ral::write_reg!(ocotp, self.ocotp, DATA, fuse_value);
94        Poll::Ready(Ok(()))
95    }
96
97    /// Wait for a fuse read to complete.
98    ///
99    /// You should sequence this sometime after [`start_fuse_read`](Self::start_fuse_read).
100    /// Returns `Poll::Pending` while the read is in progress. If the ECC-redundant fuse
101    /// could not be corrected, the error is [`Error::DedRead`]. The implementation will
102    /// automatically clear the DED error.
103    fn poll_fuse_read(&mut self) -> Poll<Result<u32, Error>> {
104        task::ready!(self.check_status())?;
105
106        // We're polling for errors above.
107        //
108        // Not checking against sentinel 0xBADABADA. What
109        // if someone actually wanted to write that fuse
110        // value?
111        let fuse_data = self.read_fuse_data();
112        self.check_end_fuse_read()?;
113
114        Poll::Ready(Ok(fuse_data))
115    }
116
117    /// Wait for the write to complete.
118    ///
119    /// You should sequence this sometime after [`start_fuse_write`](Self::start_fuse_write).
120    /// Returns `Poll::Pending` while the write is in progress.
121    fn poll_fuse_write(&mut self) -> Poll<Result<(), Error>> {
122        task::ready!(self.check_status())?;
123        self.check_end_fuse_write()?;
124
125        Poll::Ready(Ok(()))
126    }
127
128    /// Read a fuse, blocking indefinitely until it returns a value.
129    ///
130    /// This simply spins on the future returned by [`spin_fuse_read`](Self::spin_fuse_read).
131    /// The approach does not give you a way to specify any timeout. Use only
132    /// when you're confident the fuse read will complete.
133    pub fn blocking_fuse_read(&mut self, fuse_address: FuseAddress) -> Result<u32, Error> {
134        crate::spin_on(self.spin_fuse_read(fuse_address))
135    }
136
137    /// Write a fuse, blocking indefinitely until it completes.
138    ///
139    /// This simply spins on the future returned by [`spin_fuse_write`](Self::spin_fuse_write).
140    /// The approach does not give you a way to control a timeout. Use only when
141    /// you're confident the write will complete.
142    pub fn blocking_fuse_write(
143        &mut self,
144        fuse_address: FuseAddress,
145        fuse_value: u32,
146    ) -> Result<(), Error> {
147        crate::spin_on(self.spin_fuse_write(fuse_address, fuse_value))
148    }
149
150    /// Returns a spinning state machine that reads a fuse value.
151    ///
152    /// The returned task does not consult a waker! You're expected
153    /// to spin the CPU on `poll()` until it returns a value.
154    ///
155    /// The future is not cancel safe. There is no way to cancel an
156    /// in-flight fuse read. Therefore, errors from _prior, incomplete_
157    /// reads may be returned in a _subsequent_ access.
158    ///
159    /// You may compose your own timeouts by using another spinning
160    /// task, and selecting the winner of the timeout / read.
161    pub async fn spin_fuse_read(&mut self, fuse_address: FuseAddress) -> Result<u32, Error> {
162        core::future::poll_fn(|_| self.start_fuse_read(fuse_address)).await?;
163        let fuse_data = core::future::poll_fn(|_| self.poll_fuse_read()).await?;
164        Ok(fuse_data)
165    }
166
167    /// Returns a spinning state machine that writes a fuse value.
168    ///
169    /// The returned task does not consult a waker! You're expected
170    /// to spin the CPU on `poll()` until it returns a value.
171    ///
172    /// The future is not cancel safe. There is no way to cancel an
173    /// in-flight fuse write. Therefore, errors from _prior, incomplete_
174    /// writes may be returned in a _subsequent_ access.
175    ///
176    /// You may compose your own timeouts by using another spinning
177    /// task, and selecting the winner of the timeout / write.
178    pub async fn spin_fuse_write(
179        &mut self,
180        fuse_address: FuseAddress,
181        fuse_value: u32,
182    ) -> Result<(), Error> {
183        core::future::poll_fn(|_| self.start_fuse_write(fuse_address, fuse_value)).await?;
184        core::future::poll_fn(|_| self.poll_fuse_write()).await?;
185        Ok(())
186    }
187}
188
189/// A fuse address.
190///
191/// Consult your MCU's fuse map for more information.
192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193#[repr(transparent)]
194pub struct FuseAddress(u16);
195
196impl FuseAddress {
197    /// Construct a fuse address.
198    ///
199    /// `addr` is the fuse you intend to read or write.
200    /// The implementation converts this for access. If
201    /// the conversion fails, this returns `None`.
202    pub const fn new(addr: u16) -> Option<Self> {
203        let Some(addr) = addr.checked_sub(Ocotp::FUSE_ADDRESS_OFFSET) else {
204            return None;
205        };
206        if addr % 16 != 0 {
207            return None;
208        }
209        Some(Self(addr / 16))
210    }
211
212    /// Returns the raw fuse address.
213    pub const fn raw(&self) -> u16 {
214        self.0
215    }
216
217    /// Returns the fuse address supplied during construction.
218    const fn get(&self) -> u16 {
219        self.0 * 16 + Ocotp::FUSE_ADDRESS_OFFSET
220    }
221}
222
223impl core::fmt::Display for FuseAddress {
224    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
225        write!(f, "{:#X}", self.get())
226    }
227}
228
229#[cfg(feature = "defmt")]
230impl defmt::Format for FuseAddress {
231    fn format(&self, f: defmt::Formatter) {
232        defmt::write!(f, "{:#X}", self.get())
233    }
234}