imxrt_boot_gen/flexspi/
sequence.rs

1//! FlexSPI instructions, opcodes, and sequences
2//!
3//! Derived from the iMXRT1060 Reference Manual (Rev 2),
4//! section 27.5.8.
5
6use core::fmt;
7
8pub(crate) const INSTRUCTION_SIZE: usize = 2;
9
10/// A FlexSPI instruction
11///
12/// An `Instr` has an opcode, a pad count, and an opcode-dependent operand.
13/// Opcodes are available in the [`opcode` module](opcodes/index.html).
14///
15/// `Instr`s are used to create FlexSPI lookup table command [`Sequence`s](struct.Sequence.html).
16#[derive(Clone, Copy)]
17#[repr(transparent)]
18pub struct Instr([u8; INSTRUCTION_SIZE]);
19
20impl Instr {
21    /// Create a new FlexSPI LUT instruction
22    ///
23    /// Note that the `JUMP_ON_CS` and `STOP` opcodes are not available. However,
24    /// there are pre-defined [`JUMP_ON_CS`](constant.JUMP_ON_CS.html) and [`STOP`](constant.STOP.html)
25    /// instructions which you should use.
26    pub const fn new(opcode: Opcode, pads: Pads, operand: u8) -> Self {
27        Instr([operand, (opcode.0 << 2) | (pads as u8)])
28    }
29
30    const fn stop() -> Self {
31        Instr::new(opcodes::STOP, Pads::One /* unused */, 0)
32    }
33
34    const fn jump_on_cs() -> Self {
35        Instr::new(opcodes::JUMP_ON_CS, Pads::One /* unused */, 0)
36    }
37}
38
39impl fmt::Debug for Instr {
40    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
41        let raw = u16::from_le_bytes(self.0);
42        write!(f, "{raw:#02X}")
43    }
44}
45
46/// STOP FlexSPI instruction
47pub const STOP: Instr = Instr::stop();
48/// JUMP_ON_CS FlexSPI instruction
49pub const JUMP_ON_CS: Instr = Instr::jump_on_cs();
50
51pub(crate) const INSTRUCTIONS_PER_SEQUENCE: usize = 8;
52
53/// A collection of FlexSPI instructions
54///
55/// Each `Sequence` may have up to eight instructions. Use [`SequenceBuilder`] to create
56/// a `Sequence`. The sequences you'll require are dependent on the specific flash memory that
57/// you're interacting with.
58///
59/// `Sequence`s are used to create a [`LookupTable`](crate::flexspi::LookupTable).
60#[derive(Clone, Copy, Debug)]
61#[repr(transparent)]
62pub struct Sequence(pub(crate) [Instr; INSTRUCTIONS_PER_SEQUENCE]);
63pub(crate) const SEQUENCE_SIZE: usize = INSTRUCTIONS_PER_SEQUENCE * INSTRUCTION_SIZE;
64
65impl Sequence {
66    pub(crate) const fn stopped() -> Self {
67        Sequence([STOP; INSTRUCTIONS_PER_SEQUENCE])
68    }
69}
70
71/// A [`Sequence`] builder
72///
73/// Use `SequenceBuilder` to define a FlexSPI LUT sequence. If you insert too many instructions
74/// into the sequence, you'll observe a compile-time error.
75///
76/// Any unspecified instructions are set to [`STOP`].
77///
78/// # Example
79///
80/// ```
81/// use imxrt_boot_gen::flexspi::{
82///     Sequence,
83///     SequenceBuilder,
84///     Instr,
85///     Pads,
86///     opcodes::sdr::*,
87/// };
88///
89/// const SEQ_READ: Sequence = SequenceBuilder::new()
90///     .instr(Instr::new(CMD, Pads::One, 0xEB))
91///     .instr(Instr::new(READ, Pads::Four, 0x04))
92///     .build();
93/// ```
94pub struct SequenceBuilder {
95    sequence: Sequence,
96    offset: usize,
97}
98
99impl Default for SequenceBuilder {
100    fn default() -> Self {
101        Self::new()
102    }
103}
104
105impl SequenceBuilder {
106    /// Creates a new `SequenceBuilder` than can accept up to eight instructions
107    ///
108    /// All unspecified instructions are set to [`STOP`].
109    pub const fn new() -> Self {
110        SequenceBuilder {
111            sequence: Sequence::stopped(),
112            offset: 0,
113        }
114    }
115    /// Insert `instr` as the next sequence instruction
116    ///
117    /// If you call `instr` more than 8 times, you'll observe a compile-time error.
118    pub const fn instr(self, instr: Instr) -> Self {
119        let mut seq = self.sequence.0;
120        seq[self.offset] = instr;
121        SequenceBuilder {
122            sequence: Sequence(seq),
123            offset: self.offset + 1,
124        }
125    }
126    /// Create the sequence
127    pub const fn build(self) -> Sequence {
128        self.sequence
129    }
130}
131
132/// A FlexSPI opcode
133///
134/// Available `Opcode`s are defined in the `opcodes` module.
135#[derive(Clone, Copy, PartialEq, Eq)]
136pub struct Opcode(u8);
137
138/// Number of pads to use to execute the instruction
139#[derive(Clone, Copy)]
140#[repr(u8)]
141pub enum Pads {
142    /// Single mode
143    One = 0x00,
144    /// Dual mode
145    Two = 0x01,
146    /// Quad mode
147    Four = 0x02,
148    /// Octal mode
149    Eight = 0x03,
150}
151
152impl fmt::Display for Pads {
153    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
154        let pads = match *self {
155            Pads::One => "SINGLE",
156            Pads::Two => "DUAL",
157            Pads::Four => "QUAD",
158            Pads::Eight => "OCTAL",
159        };
160        write!(f, "{pads}")
161    }
162}
163
164impl fmt::Debug for Pads {
165    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
166        write!(f, "{:#02X}", *self as u8)
167    }
168}
169
170/// FlexSPI lookup table instruction opcodes
171///
172/// Opcodes are separated by their data transfer rates. General opcodes
173/// are in the top-level `opcodes` module.
174pub mod opcodes {
175    use super::Opcode;
176
177    /// Single data transfer rate (SDR) opcodes
178    pub mod sdr {
179        use super::Opcode;
180        /// Transmit command code to flash
181        pub const CMD: Opcode = Opcode(0x01);
182        /// Transmit row address to flash
183        pub const RADDR: Opcode = Opcode(0x02);
184        /// Transmit column address to flash
185        pub const CADDR: Opcode = Opcode(0x03);
186        /// Transmit mode bits to flash
187        ///
188        /// Bit number 1
189        pub const MODE1: Opcode = Opcode(0x04);
190        /// Transmit mode bits to flash
191        ///
192        /// Bit number 2
193        pub const MODE2: Opcode = Opcode(0x05);
194        /// Transmit mode bits to flash
195        ///
196        /// Bit number 4
197        pub const MODE4: Opcode = Opcode(0x06);
198        /// Transmit mode bits to flash
199        ///
200        /// Bit number 8
201        pub const MODE8: Opcode = Opcode(0x07);
202        /// Transmit programming data to flash
203        pub const WRITE: Opcode = Opcode(0x08);
204        /// Receive data from flash
205        ///
206        /// Read Data is put into AHB_RX_BUF or IP_RX_FIFO.
207        pub const READ: Opcode = Opcode(0x09);
208        /// Receive Read Data or Preamble bit from Flash device
209        ///
210        /// FlexSPI Controller will compare the data line bits with DLPR
211        /// register to determine a correct sampling clock phase.
212        pub const LEARN: Opcode = Opcode(0x0A);
213        /// Transmit Read/ Program Data size (byte number) to Flash
214        pub const DATASZ: Opcode = Opcode(0x0B);
215        /// Leave data lines undriven by FlexSPI controller.
216        ///
217        /// Provide turnaround cycles from host driving to device driving.
218        /// `num_pads` will determine the number of pads in input mode.
219        pub const DUMMY: Opcode = Opcode(0x0C);
220        /// Similar to `DUMMY`, but the cycle number is different
221        pub const DUMMY_RWDS: Opcode = Opcode(0x0D);
222    }
223
224    /// Stop execution, deassert CS. Next command sequence
225    /// (to the same flash device) will started from instruction pointer 0.
226    pub(super) const STOP: Opcode = Opcode(0x00);
227    /// Stop execution, deassert CS and save operand[7:0]
228    /// as the instruction start pointer for next sequence.
229    ///
230    /// Normally this instruction is used to support XIP enhance mode.
231    pub(super) const JUMP_ON_CS: Opcode = Opcode(0x1F);
232
233    /// Dual data transfer rate (DDR) opcodes
234    ///
235    /// See the documentation on the corresponding [`ssr` opcode](../sdr/index.html)
236    /// for more information.
237    pub mod ddr {
238        use super::Opcode;
239        use super::sdr;
240
241        /// Adds `0x20` to the opcode to make it a DDR opcode
242        const fn to_ddr(opcode: Opcode) -> Opcode {
243            Opcode(opcode.0 + 0x20)
244        }
245
246        pub const CMD: Opcode = to_ddr(sdr::CMD);
247        pub const RADDR: Opcode = to_ddr(sdr::RADDR);
248        pub const CADDR: Opcode = to_ddr(sdr::CADDR);
249        pub const MODE1: Opcode = to_ddr(sdr::MODE1);
250        pub const MODE2: Opcode = to_ddr(sdr::MODE2);
251        pub const MODE4: Opcode = to_ddr(sdr::MODE4);
252        pub const MODE8: Opcode = to_ddr(sdr::MODE8);
253        pub const WRITE: Opcode = to_ddr(sdr::WRITE);
254        pub const READ: Opcode = to_ddr(sdr::READ);
255        pub const LEARN: Opcode = to_ddr(sdr::LEARN);
256        pub const DATASZ: Opcode = to_ddr(sdr::DATASZ);
257        pub const DUMMY: Opcode = to_ddr(sdr::DUMMY);
258        pub const DUMMY_RWDS: Opcode = to_ddr(sdr::DUMMY_RWDS);
259    }
260}
261
262impl fmt::Display for Opcode {
263    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
264        use opcodes::ddr;
265        use opcodes::sdr;
266        match *self {
267            // SDR
268            sdr::CMD => write!(f, "CMD_SDR"),
269            sdr::RADDR => write!(f, "RADDR_SDR"),
270            sdr::CADDR => write!(f, "CADDR_SDR"),
271            sdr::MODE1 => write!(f, "MODE1_SDR"),
272            sdr::MODE2 => write!(f, "MODE2_SDR"),
273            sdr::MODE4 => write!(f, "MODE4_SDR"),
274            sdr::MODE8 => write!(f, "MODE8_SDR"),
275            sdr::WRITE => write!(f, "WRITE_SDR"),
276            sdr::READ => write!(f, "READ_SDR"),
277            sdr::LEARN => write!(f, "LEARN_SDR"),
278            sdr::DATASZ => write!(f, "DATASZ_SDR"),
279            sdr::DUMMY => write!(f, "DUMMY_SDR"),
280            sdr::DUMMY_RWDS => write!(f, "DUMMY_RWDS_SDR"),
281            // DDR
282            ddr::CMD => write!(f, "CMD_DDR"),
283            ddr::RADDR => write!(f, "RADDR_DDR"),
284            ddr::CADDR => write!(f, "CADDR_DDR"),
285            ddr::MODE1 => write!(f, "MODE1_DDR"),
286            ddr::MODE2 => write!(f, "MODE2_DDR"),
287            ddr::MODE4 => write!(f, "MODE4_DDR"),
288            ddr::MODE8 => write!(f, "MODE8_DDR"),
289            ddr::WRITE => write!(f, "WRITE_DDR"),
290            ddr::READ => write!(f, "READ_DDR"),
291            ddr::LEARN => write!(f, "LEARN_DDR"),
292            ddr::DATASZ => write!(f, "DATASZ_DDR"),
293            ddr::DUMMY => write!(f, "DUMMY_DDR"),
294            ddr::DUMMY_RWDS => write!(f, "DUMMY_RWDS_DDR"),
295            // Others
296            opcodes::STOP => write!(f, "STOP"),
297            opcodes::JUMP_ON_CS => write!(f, "JUMP_ON_CS"),
298            // Should be unreachable
299            unknown => write!(f, "UNKNOWN({:#02X})", unknown.0),
300        }
301    }
302}
303
304impl fmt::Debug for Opcode {
305    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
306        write!(f, "{:#02X}", self.0)
307    }
308}
309
310#[cfg(test)]
311mod test {
312    use super::Instr;
313    use super::Pads;
314    use super::opcodes::sdr::*;
315    use super::{Sequence, SequenceBuilder};
316
317    fn seq_to_bytes(seq: Sequence) -> Vec<u8> {
318        let mut buffer = vec![0; super::SEQUENCE_SIZE];
319        buffer
320            .chunks_exact_mut(2)
321            .zip(seq.0.iter())
322            .for_each(|(dst, src)| dst.copy_from_slice(&src.0));
323        buffer
324    }
325
326    // Tests were implemented by a study of the
327    // known-good Teensy 4 FCB lookup table.
328    //
329    // See table Table 9-16. LUT sequence definition for Serial NOR,
330    // to better understand the meaning behind the sequences.
331
332    #[test]
333    fn teensy4_read() {
334        const EXPECTED: [u8; super::SEQUENCE_SIZE] = [
335            0xEB, 0x04, 0x18, 0x0A, 0x06, 0x32, 0x04, 0x26, 0, 0, 0, 0, 0, 0, 0, 0,
336        ];
337
338        const SEQUENCE: Sequence = SequenceBuilder::new()
339            .instr(Instr::new(CMD, Pads::One, 0xEB))
340            .instr(Instr::new(RADDR, Pads::Four, 0x18))
341            .instr(Instr::new(DUMMY, Pads::Four, 0x06))
342            .instr(Instr::new(READ, Pads::Four, 0x04))
343            .build();
344
345        assert_eq!(&seq_to_bytes(SEQUENCE), &EXPECTED);
346    }
347
348    #[test]
349    fn teensy4_read_status() {
350        const EXPECTED: [u8; 4] = [0x05, 0x04, 0x04, 0x24];
351        const SEQUENCE: Sequence = SequenceBuilder::new()
352            .instr(Instr::new(CMD, Pads::One, 0x05))
353            .instr(Instr::new(READ, Pads::One, 0x04))
354            .build();
355        assert_eq!(&seq_to_bytes(SEQUENCE)[0..4], &EXPECTED);
356    }
357
358    #[test]
359    fn teensy4_write_enable() {
360        const EXPECTED: u128 = 0x0000_0406;
361        const SEQUENCE: Sequence = SequenceBuilder::new()
362            .instr(Instr::new(CMD, Pads::One, 0x06))
363            .build();
364        assert_eq!(&EXPECTED.to_le_bytes(), &seq_to_bytes(SEQUENCE)[..]);
365    }
366
367    #[test]
368    fn teensy4_erase_sector() {
369        const EXPECTED: u128 = 0x0818_0420;
370        const SEQUENCE: Sequence = SequenceBuilder::new()
371            .instr(Instr::new(CMD, Pads::One, 0x20))
372            .instr(Instr::new(RADDR, Pads::One, 0x18))
373            .build();
374        assert_eq!(&EXPECTED.to_le_bytes(), &seq_to_bytes(SEQUENCE)[..]);
375    }
376
377    #[test]
378    fn teensy4_page_program() {
379        const EXPECTED: u128 = 0x0000_2004_0818_0402;
380        const SEQUENCE: Sequence = SequenceBuilder::new()
381            .instr(Instr::new(CMD, Pads::One, 0x02))
382            .instr(Instr::new(RADDR, Pads::One, 0x18))
383            .instr(Instr::new(WRITE, Pads::One, 0x04))
384            .build();
385        assert_eq!(&EXPECTED.to_le_bytes(), &seq_to_bytes(SEQUENCE)[..]);
386    }
387
388    #[test]
389    fn teensy4_chip_erase() {
390        const EXPECTED: u128 = 0x0000_0460;
391        const SEQUENCE: Sequence = SequenceBuilder::new()
392            .instr(Instr::new(CMD, Pads::One, 0x60))
393            .build();
394        assert_eq!(&EXPECTED.to_le_bytes(), &seq_to_bytes(SEQUENCE)[..]);
395    }
396}
397
398//
399// Keep these two tests in sync
400//
401// The first one lets you know if the second one is failing to compile
402// in the way we expect.
403//
404
405/// ```
406/// use imxrt_boot_gen::flexspi::{*, opcodes::sdr::*};
407/// const INSTR: Instr = Instr::new(RADDR, Pads::Four, 0x18);
408/// const OUT_OF_BOUNDS: Sequence = SequenceBuilder::new()
409///     .instr(INSTR)
410///     .instr(INSTR)
411///     .instr(INSTR)
412///     .instr(INSTR)
413///     .instr(INSTR)
414///     .instr(INSTR)
415///     .instr(INSTR)
416///     .instr(INSTR)
417///     .build();
418/// ```
419#[cfg(doctest)]
420struct SequenceBuilderInstructionLimit;
421
422/// ```compile_fail
423/// use imxrt_boot_gen::flexspi::{*, opcodes::sdr::*};
424/// const INSTR: Instr = Instr::new(RADDR, Pads::Four, 0x18);
425/// const OUT_OF_BOUNDS: Sequence = SequenceBuilder::new()
426///     .instr(INSTR)
427///     .instr(INSTR)
428///     .instr(INSTR)
429///     .instr(INSTR)
430///     .instr(INSTR)
431///     .instr(INSTR)
432///     .instr(INSTR)
433///     .instr(INSTR)
434///     .instr(INSTR) // <------- THIS SHOULD FAIL
435///     .build();
436/// ```
437#[cfg(doctest)]
438struct SequenceBuilderTooManyInstructions;