stm32f7xx-hal 0.7.0

HAL for the STM32F7xx family of microcontrollers
Documentation
//! QUADSPI driver for the STM32F7. Supports INDIRECT mode only, using DMA or polling I/O.

use as_slice::AsSlice;
use core::convert::TryInto;
use core::marker::PhantomData;
use core::ops::Deref;
use core::pin::Pin;

use crate::dma;
use crate::pac::{QUADSPI, RCC};
use crate::rcc::Enable;
use crate::state;

/// The QSPI driver interface.
pub struct Qspi {
    /// QSPI peripheral registers.
    qspi: QUADSPI,
    /// Address size for all transactions.
    adsize: u8,
}

/// QSPI transaction description. Note that "advanced" settings like DDRM, DHHC,
/// SIOO, and the use of alternate bytes are not supported at the moment.
#[derive(Clone)]
pub struct QspiTransaction {
    pub iwidth: u8,
    pub awidth: u8,
    pub dwidth: u8,
    pub instruction: u8,
    pub address: Option<u32>,
    pub dummy: u8,
    pub data_len: Option<usize>,
}

/// QSPI errors.
#[derive(Debug)]
pub enum Error {
    /// Bad input parameters.
    BadParam,
}

/// QSPI transactions contain configurable instruction, address, and data fields.
/// Use these constants for the `*width` fields in `QspiTransaction`.
pub struct QspiWidth;

#[allow(dead_code)]
impl QspiWidth {
    pub const NONE: u8 = 0b00;
    pub const SING: u8 = 0b01;
    pub const DUAL: u8 = 0b10;
    pub const QUAD: u8 = 0b11;
}

/// QSPI functional mode. Only `INDIRECT_READ` and `INDIRECT_WRITE` are
/// supported at the moment.
struct QspiMode;

#[allow(dead_code)]
impl QspiMode {
    pub const INDIRECT_WRITE: u8 = 0b00;
    pub const INDIRECT_READ: u8 = 0b01;
    pub const AUTO_POLLING: u8 = 0b10;
    pub const MEMORY_MAPPED: u8 = 0b11;
}

impl Qspi {
    /// Initialize and configure the QSPI flash driver.
    /// - `size` is log2(flash size in bytes), e.g. 16 MB = 24.
    /// - `adsize` is the number of bytes needed to specify the address (1, 2, 3, or 4).
    pub fn new(_rcc: &mut RCC, qspi: QUADSPI, size: u8, mut adsize: u8) -> Self {
        assert!((1..=4).contains(&adsize));
        adsize -= 1;

        // Enable QUADSPI in RCC
        unsafe { QUADSPI::enable_unchecked() };

        // Configure QSPI
        unsafe {
            // Single flash mode with a QSPI clock prescaler of 2 (216 / 2 = 108 MHz), FIFO
            // threshold only matters for DMA and is set to 4 to allow word sized DMA requests
            qspi.cr
                .write_with_zero(|w| w.prescaler().bits(1).fthres().bits(3).en().set_bit());

            // Set the device size
            qspi.dcr.write_with_zero(|w| w.fsize().bits(size - 1));
        }

        Qspi { qspi, adsize }
    }

    /// DMA read. Wrapper around the HAL DMA driver. Performs QSPI register programming, creates a
    /// DMA transfer from peripheral to memory, and starts the transfer. Caller can use the DMA
    /// `wait` API to block until the transfer is complete.
    pub fn read_all<B>(
        &mut self,
        data: Pin<B>,
        transaction: QspiTransaction,
        dma: &dma::Handle<<RxTx<QUADSPI> as dma::Target>::Instance, state::Enabled>,
        stream: <RxTx<QUADSPI> as dma::Target>::Stream,
    ) -> Result<dma::Transfer<RxTx<QUADSPI>, B, dma::Started>, Error>
    where
        B: Deref + 'static,
        B::Target: AsSlice<Element = u8>,
    {
        // Only use DMA with data, for command only use `polling_read`
        match transaction.data_len {
            Some(data_len) => {
                assert!(
                    (data_len as u32) % 4 == 0,
                    "DMA transfer must be word aligned."
                );

                // Setup the transaction registers
                self.setup_transaction(QspiMode::INDIRECT_READ, &transaction);

                // Setup DMA transfer
                let rx_token = RxTx(PhantomData);
                let rx_transfer = unsafe {
                    dma::Transfer::new(
                        dma,
                        stream,
                        data,
                        rx_token,
                        self.dr_address(),
                        dma::Direction::PeripheralToMemory,
                    )
                };

                let rx_transfer = rx_transfer.start(dma);

                // Set DMA bit since we are using it
                self.qspi.cr.modify(|_, w| w.dmaen().set_bit());

                Ok(rx_transfer)
            }
            None => Err(Error::BadParam),
        }
    }

    /// DMA write. Wrapper around the HAL DMA driver. Performs QSPI register programming, creates a
    /// DMA transfer from memory to peripheral, and starts the transfer. Caller can use the DMA
    /// `wait` API to block until the transfer is complete.
    pub fn write_all<B>(
        &mut self,
        data: Pin<B>,
        transaction: QspiTransaction,
        dma: &dma::Handle<<RxTx<QUADSPI> as dma::Target>::Instance, state::Enabled>,
        stream: <RxTx<QUADSPI> as dma::Target>::Stream,
    ) -> Result<dma::Transfer<RxTx<QUADSPI>, B, dma::Started>, Error>
    where
        B: Deref + 'static,
        B::Target: AsSlice<Element = u8>,
    {
        // Only use DMA with data, for command only use `polling_write`
        match transaction.data_len {
            Some(data_len) => {
                assert!(
                    (data_len as u32) % 4 == 0,
                    "DMA transfer must be word aligned."
                );

                // Setup the transaction registers
                self.setup_transaction(QspiMode::INDIRECT_WRITE, &transaction);

                // Setup DMA transfer
                let tx_token = RxTx(PhantomData);
                let tx_transfer = unsafe {
                    dma::Transfer::new(
                        dma,
                        stream,
                        data,
                        tx_token,
                        self.dr_address(),
                        dma::Direction::MemoryToPeripheral,
                    )
                };

                let tx_transfer = tx_transfer.start(dma);

                // Set DMA bit since we are using it
                self.qspi.cr.modify(|_, w| w.dmaen().set_bit());

                Ok(tx_transfer)
            }
            None => Err(Error::BadParam),
        }
    }

    /// Polling indirect read. Can also be used to perform transactions with no data.
    pub fn read(&mut self, buf: &mut [u8], transaction: QspiTransaction) -> Result<(), Error> {
        // Clear DMA bit since we are not using it
        self.qspi.cr.modify(|_, w| w.dmaen().clear_bit());

        // Setup the transaction registers
        self.setup_transaction(QspiMode::INDIRECT_READ, &transaction);

        // If the transaction has data, read it word-by-word from the data register
        if let Some(len) = transaction.data_len {
            let mut idx: usize = 0;
            while idx < len {
                // Check if there are bytes in the FIFO
                let num_bytes = self.qspi.sr.read().flevel().bits();
                if num_bytes > 0 {
                    // Read a word
                    let word = self.qspi.dr.read().data().bits();

                    // Unpack the word
                    let num_unpack = if num_bytes >= 4 { 4 } else { num_bytes };
                    for i in 0..num_unpack {
                        buf[idx] = ((word & (0xFF << (i * 8))) >> (i * 8)).try_into().unwrap();
                        idx += 1;
                    }
                }
            }
        }

        Ok(())
    }

    /// Polling indirect write.
    pub fn write(&mut self, buf: &[u8], transaction: QspiTransaction) -> Result<(), Error> {
        // Clear DMA bit since we are not using it
        self.qspi.cr.modify(|_, w| w.dmaen().clear_bit());

        // Setup the transaction registers
        self.setup_transaction(QspiMode::INDIRECT_WRITE, &transaction);

        // If the transaction has data, write it word-by-word to the data register
        if let Some(len) = transaction.data_len {
            let mut idx: usize = 0;
            while idx < len {
                // Check if the FIFO is empty
                let num_bytes = self.qspi.sr.read().flevel().bits();
                if num_bytes == 0 {
                    // Pack the word
                    let mut word: u32 = 0;
                    let num_pack = if (len - idx) >= 4 { 4 } else { len - idx };
                    for i in 0..num_pack {
                        word |= (buf[idx] as u32) << (i * 8);
                        idx += 1;
                    }

                    // Write a word
                    unsafe {
                        self.qspi.dr.write(|w| w.data().bits(word));
                    }
                }
            }
        }

        Ok(())
    }

    /// Map from QspiTransaction to QSPI registers.
    fn setup_transaction(&mut self, fmode: u8, transaction: &QspiTransaction) {
        unsafe {
            // Clear any prior status flags
            self.qspi.fcr.write(|w| w.bits(0x1B));

            // Update data length, if applicable
            if let Some(len) = transaction.data_len {
                self.qspi.dlr.write(|w| w.bits(len as u32 - 1));
            }

            // Update CCR register with metadata
            self.qspi.ccr.write_with_zero(|w| {
                w.fmode()
                    .bits(fmode)
                    .imode()
                    .bits(transaction.iwidth)
                    .admode()
                    .bits(transaction.awidth)
                    .dmode()
                    .bits(transaction.dwidth)
                    .adsize()
                    .bits(self.adsize)
                    .abmode()
                    .bits(QspiWidth::NONE)
                    .dcyc()
                    .bits(transaction.dummy)
                    .instruction()
                    .bits(transaction.instruction)
            });

            // Update address register, if applicable
            if let Some(addr) = transaction.address {
                self.qspi.ar.write(|w| w.bits(addr));
            }
        }
    }

    /// Get data register address.
    fn dr_address(&self) -> u32 {
        &self.qspi.dr as *const _ as _
    }
}

/// Token used for DMA transfers.
pub struct RxTx<I>(PhantomData<I>);