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}