atsamd_hal/peripherals/
qspi.rs

1use crate::{
2    gpio::{AlternateH, AnyPin, Pin, PA08, PA09, PA10, PA11, PB10, PB11},
3    pac::qspi::instrframe,
4    pac::{self, Mclk},
5};
6use core::marker::PhantomData;
7
8#[derive(Debug, Clone, Copy, Eq, PartialEq)]
9#[cfg_attr(feature = "defmt", derive(defmt::Format))]
10pub enum Error {
11    /// The command you selected cannot be performed by this function
12    CommandFunctionMismatch,
13}
14
15/// Qspi used for read/write of fixed-size octet buffers
16pub struct OneShot;
17/// Qspi is memory-mapped as read/execute
18pub struct XIP;
19
20pub struct Qspi<MODE> {
21    qspi: pac::Qspi,
22    _sck: Pin<PB10, AlternateH>,
23    _cs: Pin<PB11, AlternateH>,
24    _io0: Pin<PA08, AlternateH>,
25    _io1: Pin<PA09, AlternateH>,
26    _io2: Pin<PA10, AlternateH>,
27    _io3: Pin<PA11, AlternateH>,
28    _mode: PhantomData<MODE>,
29}
30
31impl Qspi<OneShot> {
32    /// Enable the clocks for the qspi peripheral in single data rate mode
33    /// assuming 120mhz system clock, for 4mhz spi mode 0 operation.
34    #[allow(clippy::too_many_arguments)]
35    pub fn new(
36        mclk: &mut Mclk,
37        qspi: pac::Qspi,
38        _sck: impl AnyPin<Id = PB10>,
39        _cs: impl AnyPin<Id = PB11>,
40        _io0: impl AnyPin<Id = PA08>,
41        _io1: impl AnyPin<Id = PA09>,
42        _io2: impl AnyPin<Id = PA10>,
43        _io3: impl AnyPin<Id = PA11>,
44    ) -> Qspi<OneShot> {
45        mclk.apbcmask().modify(|_, w| w.qspi_().set_bit());
46        // Enable the clocks for the qspi peripheral in single data rate mode.
47        mclk.ahbmask().modify(|_, w| {
48            w.qspi_().set_bit();
49            w.qspi_2x_().clear_bit()
50        });
51
52        let _sck = _sck.into().into_alternate();
53        let _cs = _cs.into().into_alternate();
54        let _io0 = _io0.into().into_alternate();
55        let _io1 = _io1.into().into_alternate();
56        let _io2 = _io2.into().into_alternate();
57        let _io3 = _io3.into().into_alternate();
58
59        qspi.ctrla().write(|w| w.swrst().set_bit());
60        qspi.baud().write(|w| unsafe {
61            // TODO get system clock value instead of hardcoding
62            //(120_000_000u32 / 4_000_000u32) = 30 = BAUD + 1
63            // BAUD = 29
64            w.baud().bits(29); // 4Mhz
65                               // SPI MODE 0
66            w.cpol().clear_bit();
67            w.cpha().clear_bit()
68        });
69
70        qspi.ctrlb().write(|w| {
71            w.mode().memory();
72            w.csmode().noreload();
73            w.csmode().lastxfer();
74            w.datalen()._8bits()
75        });
76
77        qspi.ctrla().modify(|_, w| w.enable().set_bit());
78
79        Self {
80            qspi,
81            _sck,
82            _cs,
83            _io0,
84            _io1,
85            _io2,
86            _io3,
87            _mode: PhantomData,
88        }
89    }
90
91    /// Run a generic command that neither takes nor receives data
92    pub fn run_command(&self, command: Command) -> Result<(), Error> {
93        match command {
94            //TODO verify this list of commands
95            Command::WriteEnable
96            | Command::WriteDisable
97            | Command::Reset
98            | Command::EnableReset => (),
99            _ => return Err(Error::CommandFunctionMismatch),
100        }
101
102        let tfm = TransferMode {
103            instruction_enable: true,
104            ..TransferMode::default()
105        };
106        unsafe {
107            self.run_read_instruction(command, tfm, 0, &mut [], true);
108        }
109        Ok(())
110    }
111
112    /// Run one of the read commands
113    pub fn read_command(&self, command: Command, response: &mut [u8]) -> Result<(), Error> {
114        match command {
115            //TODO verify this list of commands
116            Command::Read
117            | Command::QuadRead
118            | Command::ReadId
119            | Command::ReadStatus
120            | Command::ReadStatus2 => (),
121            _ => return Err(Error::CommandFunctionMismatch),
122        }
123
124        let tfm = TransferMode {
125            data_enable: true,
126            instruction_enable: true,
127            ..TransferMode::default()
128        };
129        unsafe {
130            self.run_read_instruction(command, tfm, 0, response, true);
131        }
132        Ok(())
133    }
134
135    /// Run one of the write commands
136    pub fn write_command(&self, command: Command, data: &[u8]) -> Result<(), Error> {
137        match command {
138            //TODO verify this list of commands
139            Command::PageProgram
140            | Command::QuadPageProgram
141            | Command::WriteStatus
142            | Command::WriteStatus2 => (),
143            _ => return Err(Error::CommandFunctionMismatch),
144        }
145
146        let tfm = TransferMode {
147            data_enable: !data.is_empty(),
148            instruction_enable: true,
149            ..TransferMode::default()
150        };
151        unsafe {
152            self.run_write_instruction(command, tfm, 0, data);
153        }
154        Ok(())
155    }
156
157    /// Run one of the erase commands
158    pub fn erase_command(&self, command: Command, address: u32) -> Result<(), Error> {
159        match command {
160            //TODO verify this list of commands
161            Command::EraseSector | Command::EraseBlock => {
162                let tfm = TransferMode {
163                    address_enable: true,
164                    instruction_enable: true,
165                    ..TransferMode::default()
166                };
167                unsafe {
168                    self.run_write_instruction(command, tfm, address, &[]);
169                }
170            }
171            Command::EraseChip => {
172                let tfm = TransferMode {
173                    instruction_enable: true,
174                    ..TransferMode::default()
175                };
176                unsafe {
177                    self.run_read_instruction(command, tfm, 0, &mut [], true);
178                }
179            }
180            _ => return Err(Error::CommandFunctionMismatch),
181        }
182
183        Ok(())
184    }
185
186    /// Quad Fast Read a sequential block of memory to buf
187    /// Note: Hardcodes 8 dummy cycles
188    pub fn read_memory(&mut self, addr: u32, buf: &mut [u8]) {
189        let tfm = TransferMode {
190            quad_width: true,
191            address_enable: true,
192            data_enable: true,
193            instruction_enable: true,
194            dummy_cycles: 8,
195            ..TransferMode::default()
196        };
197        unsafe { self.run_read_instruction(Command::QuadRead, tfm, addr, buf, true) };
198    }
199
200    /// Page Program a sequential block of memory to addr.
201    ///
202    /// Note more than page size bytes are sent to the device, some bytes will
203    /// be discarded. Check your device for specific handling.
204    pub fn write_memory(&mut self, addr: u32, buf: &[u8]) {
205        let tfm = TransferMode {
206            quad_width: true,
207            address_enable: true,
208            data_enable: true,
209            instruction_enable: true,
210            ..TransferMode::default()
211        };
212        unsafe { self.run_write_instruction(Command::QuadPageProgram, tfm, addr, buf) };
213    }
214
215    /// Latches the peripheral in a read/execute state, so it can be used to
216    /// read or execute directly from flash.
217    ///
218    /// Note: Hardcodes 8 dummy cycles.
219    pub fn into_xip(self) -> Qspi<XIP> {
220        let tfm = TransferMode {
221            quad_width: true,
222            address_enable: true,
223            data_enable: true,
224            instruction_enable: true,
225            dummy_cycles: 8,
226            ..TransferMode::default()
227        };
228        unsafe {
229            self.run_read_instruction(Command::QuadRead, tfm, 0, &mut [], false);
230        }
231
232        Qspi::<XIP> {
233            qspi: self.qspi,
234            _sck: self._sck,
235            _cs: self._cs,
236            _io0: self._io0,
237            _io1: self._io1,
238            _io2: self._io2,
239            _io3: self._io3,
240            _mode: PhantomData,
241        }
242    }
243
244    /// Return the consumed pins and the Qspi peripheral
245    ///
246    /// Order: `(qspi, sck, cs, io0, io1, io2, io3)`
247    #[allow(clippy::type_complexity)]
248    pub fn free(
249        self,
250    ) -> (
251        pac::Qspi,
252        Pin<PB10, AlternateH>,
253        Pin<PB11, AlternateH>,
254        Pin<PA08, AlternateH>,
255        Pin<PA09, AlternateH>,
256        Pin<PA10, AlternateH>,
257        Pin<PA11, AlternateH>,
258    ) {
259        (
260            self.qspi, self._sck, self._cs, self._io0, self._io1, self._io2, self._io3,
261        )
262    }
263}
264
265/// Operations available in XIP mode
266impl Qspi<XIP> {
267    /// Latches the peripheral in a read/execute state, so it can be used to
268    /// read or execute directly from flash.
269    pub fn into_oneshot(self) -> Qspi<OneShot> {
270        unsafe { self.finalize() };
271
272        Qspi::<OneShot> {
273            qspi: self.qspi,
274            _sck: self._sck,
275            _cs: self._cs,
276            _io0: self._io0,
277            _io1: self._io1,
278            _io2: self._io2,
279            _io3: self._io3,
280            _mode: PhantomData,
281        }
282    }
283}
284
285// (Mostly internal) methods available in any mode.
286impl<MODE> Qspi<MODE> {
287    unsafe fn finalize(&self) {
288        self.qspi.ctrla().write(|w| {
289            w.enable().set_bit();
290            w.lastxfer().set_bit()
291        });
292
293        while self.qspi.intflag().read().instrend().bit_is_clear() {}
294        self.qspi.intflag().write(|w| w.instrend().set_bit());
295        while self.qspi.intflag().read().csrise().bit_is_clear() {}
296        self.qspi.intflag().write(|w| w.csrise().set_bit());
297    }
298
299    unsafe fn run_write_instruction(
300        &self,
301        command: Command,
302        tfm: TransferMode,
303        addr: u32,
304        buf: &[u8],
305    ) {
306        if command == Command::EraseSector || command == Command::EraseBlock {
307            self.qspi.instraddr().write(|w| w.addr().bits(addr));
308        }
309        self.qspi
310            .instrctrl()
311            .modify(|_, w| w.instr().bits(command.bits()));
312        self.qspi.instrframe().write(|w| {
313            tfm.instrframe(
314                w,
315                if command == Command::QuadPageProgram {
316                    instrframe::Tfrtypeselect::Writememory
317                } else {
318                    instrframe::Tfrtypeselect::Write
319                },
320            )
321        });
322        self.qspi.instrframe().read().bits();
323
324        if !buf.is_empty() {
325            core::ptr::copy(buf.as_ptr(), (QSPI_AHB + addr) as *mut u8, buf.len());
326        }
327
328        self.finalize();
329    }
330
331    unsafe fn run_read_instruction(
332        &self,
333        command: Command,
334        tfm: TransferMode,
335        addr: u32,
336        buf: &mut [u8],
337        finalize: bool,
338    ) {
339        self.qspi
340            .instrctrl()
341            .modify(|_, w| w.instr().bits(command.bits()));
342        self.qspi.instrframe().write(|w| {
343            tfm.instrframe(
344                w,
345                if command == Command::QuadRead {
346                    instrframe::Tfrtypeselect::Readmemory
347                } else {
348                    instrframe::Tfrtypeselect::Read
349                },
350            )
351        });
352        self.qspi.instrframe().read().bits();
353
354        if !buf.is_empty() {
355            core::ptr::copy((QSPI_AHB + addr) as *mut u8, buf.as_mut_ptr(), buf.len());
356        }
357
358        if finalize {
359            self.finalize();
360        }
361    }
362
363    /// Set the clock divider, relative to the main clock
364    ///
365    /// This fn safely subtracts 1 from your input value as the underlying fn is
366    /// SCK Baud = MCKL / (value + 1)
367    ///
368    /// ex if Mclk is 120mhz
369    /// value  0 is reduced to  0 results in 120mhz clock
370    /// value  1 is reduced to  0 results in 120mhz clock
371    /// value  2 is reduced to  1 results in  60mhz clock
372    pub fn set_clk_divider(&mut self, value: u8) {
373        // The baud register is divisor - 1
374        self.qspi
375            .baud()
376            .write(|w| unsafe { w.baud().bits(value.saturating_sub(1)) });
377    }
378}
379
380#[derive(Default, Debug, Copy, Clone)]
381struct TransferMode {
382    quad_width: bool,
383    data_enable: bool,
384    opcode_enable: bool,
385    address_enable: bool,
386    instruction_enable: bool,
387    dummy_cycles: u8,
388}
389
390impl TransferMode {
391    unsafe fn instrframe(
392        self,
393        instrframe: &mut instrframe::W,
394        tfrtype: instrframe::Tfrtypeselect,
395    ) -> &mut instrframe::W {
396        if self.quad_width {
397            instrframe.width().quad_output();
398        } else {
399            instrframe.width().single_bit_spi();
400        }
401
402        if self.data_enable {
403            instrframe.dataen().set_bit();
404        }
405        if self.opcode_enable {
406            instrframe.dataen().set_bit();
407        }
408        if self.address_enable {
409            instrframe.addren().set_bit();
410        }
411        if self.instruction_enable {
412            instrframe.instren().set_bit();
413        }
414
415        if self.dummy_cycles > 0 {
416            instrframe.dummylen().bits(self.dummy_cycles);
417        }
418        instrframe.addrlen()._24bits();
419        instrframe.optcodeen().clear_bit();
420        instrframe.tfrtype().variant(tfrtype);
421        instrframe
422    }
423}
424
425#[repr(u8)]
426#[derive(Debug, Copy, Clone, Eq, PartialEq)]
427pub enum Command {
428    Read = 0x03,
429    QuadRead = 0x6B,
430    ReadId = 0x9F,
431    PageProgram = 0x02,
432    QuadPageProgram = 0x32,
433    ReadStatus = 0x05,
434    ReadStatus2 = 0x35,
435    WriteStatus = 0x01,
436    WriteStatus2 = 0x31,
437    EnableReset = 0x66,
438    Reset = 0x99,
439    WriteEnable = 0x06,
440    WriteDisable = 0x04,
441    EraseSector = 0x20,
442    EraseBlock = 0xD8,
443    EraseChip = 0xC7,
444}
445
446impl Command {
447    fn bits(self) -> u8 {
448        self as u8
449    }
450}
451
452const QSPI_AHB: u32 = 0x04000000;