stm32-hal2 0.2.11

Hardware abstraction layer for the stm32 chips
Documentation
//! Quad Serial Peripheral Interface (SPI) bus: A specialized interface used for
//! high-speed communications with external flash memory.

use crate::{
    clocks::Clocks,
    pac::{QUADSPI, RCC},
};

use core::ptr;

use cortex_m::interrupt::free;

use cfg_if::cfg_if;

// todo: Status-polling mode.

// todo: Is this avail in PAC? Feature-gate if diff on diff platforms?
const MEM_MAPPED_BASE_ADDR: usize = 0x9000_0000;

#[derive(Copy, Clone)]
#[repr(u8)]
/// Sets the Qspi mode to single, dual, or quad. Affects the IMODE, ADMODE, ABMODE,
/// and DMODE fields of the CCR reg. Each of these fields affects a different mode of operation.
pub enum ProtocolMode {
    /// Only a single IO line (IO0) is used for transmit and a separate line (IO1) is used for receive.
    Single = 0b01,
    /// Two IO lines (IO0 and IO1) are used for transmit/receive.
    Dual = 0b10,
    /// All four IO lines are used for transmit/receive.
    Quad = 0b11,
}

#[derive(Copy, Clone)]
#[repr(u8)]
/// Sets the Qspi data mode. Affects the DDRM field of the CCR reg.
pub enum DataMode {
    /// In SDR mode, when the QUADSPI is driving the IO0/SO, IO1, IO2, IO3 signals, these
    /// signals transition only with the falling edge of CLK.
    Sdr = 0,
    /// In DDR mode, when the QUADSPI is driving the IO0/SO, IO1, IO2, IO3 signals in the
    /// address/alternate-byte/data phases, a bit is sent on each of the falling and rising edges of
    /// CLK.
    Ddr = 1,
}

#[derive(Copy, Clone)]
#[repr(u8)]
/// Sets the Qspi Functional Mode. Affects the FMODE field of the CCR reg.
pub enum FunctionalMode {
    IndirectWrite = 0b00,
    IndirectRead = 0b01,
    StatusPolling = 0b10,
    MemoryMapped = 0b11,
}

/// Address sizes used by the QSPI interface
#[derive(Copy, Clone, PartialEq)]
pub enum AddressSize {
    /// 8 byte address size.
    A8 = 0b00,
    /// 16 byte address size.
    A16 = 0b01,
    /// 24 byte address size.
    A24 = 0b10,
    /// 32 byte address size.
    A32 = 0b11,
}

/// Sampling mode for the QSPI interface
#[derive(Copy, Clone, PartialEq)]
pub enum SamplingEdge {
    Rising = 0,
    Falling = 1,
}

/// Indicates an error with the QSPI peripheral.
#[derive(Copy, Clone, PartialEq)]
pub enum QspiError {
    Busy,
    Underflow,
}

// todo: Use bank on suitable MCUs? Which? F7 / H7?

/// A structure for specifying QSPI configuration.
#[derive(Copy, Clone)]
pub struct QspiConfig {
    pub protocol_mode: ProtocolMode,
    pub data_mode: DataMode,
    pub frequency: u32,
    pub address_size: AddressSize,
    pub dummy_cycles: u8,
    pub sampling_edge: SamplingEdge,
    pub fifo_threshold: u8,
    pub mem_size: u32, // Size of the memory, in Megabytes.
}

impl Default for QspiConfig {
    fn default() -> Self {
        Self {
            // todo: QC what you want here.
            protocol_mode: ProtocolMode::Quad,
            data_mode: DataMode::Sdr,
            frequency: 40_000_000, // todo: What should this be?
            address_size: AddressSize::A8,
            dummy_cycles: 0,
            sampling_edge: SamplingEdge::Falling,
            fifo_threshold: 1, // todo: What is this?
            mem_size: 64,
        }
    }
}

/// Interrupt events
#[derive(Copy, Clone, PartialEq)]
pub enum QspiInterrupt {
    FifoThreshold,
    StatusMatch,
    Timeout,
    TransferComplete,
    TransferError,
}

/// Represents a Quad Serial Peripheral Interface (QSPI) peripheral.
pub struct Qspi {
    regs: QUADSPI,
    cfg: QspiConfig,
}

impl Qspi {
    pub fn new(regs: QUADSPI, cfg: QspiConfig, clocks: &Clocks) -> Self {
        assert!(
            cfg.dummy_cycles < 32,
            "Dumy cycles must be between 0 and 31."
        );

        free(|cs| {
            let mut rcc = unsafe { &(*RCC::ptr()) };
            // cfg_if! {
            //     if #[cfg(any(feature = "l4", feature = "l5", feature = "")] {
            //         rcc.ahb3enr.modify(|_, w| w.qspien().set_bit());
            //         rcc.ahb3rstr.modify(|_, w| w.qspirst().set_bit());
            //         rcc.ahb3rstr.modify(|_, w| w.qspirst().clear_bit());
            //     } else { // G and H7
            //         rcc.ahb3enr.modify(|_, w| w.qspien().set_bit());
            //         rcc.ahb3rstr.modify(|_, w| w.qspirst().set_bit());
            //         rcc.ahb3rstr.modify(|_, w| w.qspirst().clear_bit());
            //     }
            // }
            rcc.ahb3enr.modify(|_, w| w.qspien().set_bit());
            rcc.ahb3rstr.modify(|_, w| w.qspirst().set_bit());
            rcc.ahb3rstr.modify(|_, w| w.qspirst().clear_bit());
        });

        // Disable QUADSPI before configuring it.
        regs.cr.write(|w| w.en().clear_bit());

        // Many fields, including all CCR fields, can only be set when `BUSY` is clear.
        while regs.sr.read().busy().bit_is_set() {}

        regs.ccr.modify(|_, w| unsafe {
            w.abmode().bits(cfg.protocol_mode as u8);
            w.admode().bits(cfg.protocol_mode as u8);
            w.imode().bits(cfg.protocol_mode as u8);
            w.dmode().bits(cfg.protocol_mode as u8);
            w.ddrm().bit(cfg.data_mode as u8 != 0);
            w.adsize().bits(cfg.address_size as u8);
            w.dcyc().bits(cfg.dummy_cycles)
        });

        // RM: The FSIZE[4:0] field defines the size of external memory using the following formula:
        // Number of bytes in Flash memory = 2^[FSIZE+1]
        // The addressable space in memory-mapped mode is limited to 256MB.
        // regs.dcr.modify(|_, w| unsafe { w.fsize.bits(cfg.mem_size / 2 - 1) });

        let mut fsize = 0;
        for shift in 0..32 {
            if cfg.mem_size >> shift == 0 {
                fsize = shift - 1;
                break;
            }
        }

        regs.dcr.modify(|_, w| unsafe { w.fsize().bits(fsize) }); // todo
                                                                  // regs.dcr.modify(|_, w| unsafe { w.fsize().bits(24) });

        // RM: This field [prescaler] defines the scaler factor for generating CLK based on the
        // clock (value+1).
        // 0: FCLK = F, clock used directly as QUADSPI CLK (prescaler bypassed)
        // 1: FCLK = F/2
        // 2: FCLK = F/3
        // ...
        // 255: FCLK = F/256
        // todo: What bus is QSPI on? is it selectable? SAI etc?? APB2 as placeholder.
        let prescaler = match (clocks.apb2() + cfg.frequency - 1) / cfg.frequency {
            divisor @ 1..=256 => divisor - 1,
            _ => panic!("Invalid QSPI frequency requested"),
        };

        let sampling_edge = match cfg.data_mode {
            // When receiving data in SDR mode, the QUADSPI assumes that the Flash memories also
            // send the data using CLK’s falling edge. By default (when SSHIFT = 0), the signals are
            // sampled using the following (rising) edge of CLK.
            DataMode::Sdr => cfg.sampling_edge,
            // When receiving data in DDR mode, the QUADSPI assumes that the Flash memories also
            // send the data using both rising and falling CLK edges. When DDRM = 1, firmware must
            // clear SSHIFT bit (bit 4 of QUADSPI_CR). Thus, the signals are sampled one half of a CLK
            // cycle later (on the following, opposite edge).
            DataMode::Ddr => SamplingEdge::Rising,
        };

        regs.cr.write(|w| unsafe {
            w.prescaler().bits(prescaler as u8);
            w.sshift().bit(sampling_edge as u8 != 0);
            w.fthres().bits(cfg.fifo_threshold - 1)
        });

        // Enable ther peripheral
        regs.cr.modify(|_, w| w.en().set_bit());

        Self { regs, cfg }
    }

    /// Check if the QSPI peripheral is currently busy with a transaction
    pub fn is_busy(&self) -> bool {
        self.regs.sr.read().busy().bit_is_set()
    }

    /// Enable an interrupt
    pub fn enable_interrupt(&mut self, interrupt: QspiInterrupt) {
        self.regs.cr.modify(|_, w| match interrupt {
            QspiInterrupt::FifoThreshold => w.ftie().set_bit(),
            QspiInterrupt::StatusMatch => w.smie().set_bit(),
            QspiInterrupt::TransferComplete => w.tcie().set_bit(),
            QspiInterrupt::Timeout => w.toie().set_bit(),
            QspiInterrupt::TransferError => w.teie().set_bit(),
        });
    }

    /// Clear an interrupt flag
    pub fn clear_interrupt(&mut self, interrupt: QspiInterrupt) {
        self.regs.fcr.modify(|_, w| match interrupt {
            QspiInterrupt::FifoThreshold => panic!("Can't clear that interrupt manually."),
            QspiInterrupt::StatusMatch => w.csmf().set_bit(),
            QspiInterrupt::TransferComplete => w.ctcf().set_bit(),
            QspiInterrupt::Timeout => w.ctof().set_bit(),
            QspiInterrupt::TransferError => w.ctef().set_bit(),
        });
    }

    /// Perform a memory write in indirect mode.
    pub fn write_indirect(&mut self, addr: u32, data: &[u8]) {
        // todo: Do we want to use interrupt flats in these blocking fns?
        self.clear_interrupt(QspiInterrupt::TransferComplete);
        // FMODE, and perhaps othe rfields can only be set when BUSY = 0.
        while self.is_busy() {}

        // todo: Fix this
        assert!(
            data.len() <= 32,
            "Transactions larger than the QSPI FIFO are currently unsupported"
        );

        // RM: Indirect Mode procedure:
        // When FMODE is programmed to 00, indirect write mode is selected and data can be sent to
        // the Flash memory. With FMODE = 01, indirect read mode is selected where data can be
        // read from the Flash memory.
        // When the QUADSPI is used in indirect mode, the frames are constructed in the following
        // way:

        // 1. Specify a number of data bytes to read or write in the QUADSPI_DLR.
        // (From DLR field description: Number of data to be retrieved (value+1) in indirect
        // and status-polling modes... 0x0000_0000: 1 byte is to be transferred etc)
        self.regs
            .dlr
            .write(|w| unsafe { w.dl().bits(data.len() as u32 - 1) });

        // 2. Specify the frame format, mode and instruction code in the QUADSPI_CCR.
        // 3. Specify optional alternate byte to be sent right after the address phase in the
        // QUADSPI_ABR.
        // (Handled in init)
        // 4. Specify the operating mode in the QUADSPI_CR. If FMODE = 00 (indirect write mode)
        // and DMAEN = 1, then QUADSPI_AR should be specified before QUADSPI_CR,
        // because otherwise QUADSPI_DR might be written by the DMA before QUADSPI_AR
        // is updated (if the DMA controller has already been enabled)
        self.regs
            .ccr
            .modify(|_, w| unsafe { w.fmode().bits(FunctionalMode::IndirectWrite as u8) });
        // 5. Specify the targeted address in the QUADSPI_AR.
        self.regs
            .ar
            .modify(|_, w| unsafe { w.address().bits(addr) });

        // 6. Read/Write the data from/to the FIFO through the QUADSPI_DR.
        // When writing the control register (QUADSPI_CR) the user specifies the following settings:
        // * The enable bit (EN) set to ‘1’
        // * The DMA enable bit (DMAEN) for transferring data to/from RAM
        // * Timeout counter enable bit (TCEN)
        // * Sample shift setting (SSHIFT)
        // * FIFO threshold level (FTRHES) to indicate when the FTF flag should be set
        // * Interrupt enables
        // * Automatic polling mode parameters: match mode and stop mode (valid when
        // FMODE = 11)
        // * Clock prescalerQuad-SPI interface (QUADSPI) RM0434
        // 400/1529 RM0434 Rev 7
        // When writing the communication configuration register (QUADSPI_CCR) the user specifies
        // the following parameters:
        // * The instruction byte through the INSTRUCTION bits
        // * The way the instruction has to be sent through the IMODE bits (1/2/4 lines)
        // * The way the address has to be sent through the ADMODE bits (None/1/2/4 lines)
        // * The address size (8/16/24/32-bit) through the ADSIZE bits
        // * The way the alternate bytes have to be sent through the ABMODE (None/1/2/4 lines)
        // * The alternate bytes number (1/2/3/4) through the ABSIZE bits
        // * The presence or not of dummy bytes through the DBMODE bit
        // * The number of dummy bytes through the DCYC bits
        // * The way the data have to be sent/received (None/1/2/4 lines) through the DMODE bits
        // (Above items handled in init)

        unsafe {
            for word in data {
                ptr::write_volatile(&self.regs.dr as *const _ as *mut u8, *word);
            }
        }

        // Wait for the transaction to complete
        while self.regs.sr.read().tcf().bit_is_clear() {}

        // Wait for the peripheral to indicate it is no longer busy.
        while self.is_busy() {}
    }

    /// Perform a memory read in indirect mode.
    pub fn read_indirect(&mut self, addr: u32, buf: &mut [u8]) -> Result<(), QspiError> {
        // todo: Do we want to use interrupt flats in these blocking fns?
        self.clear_interrupt(QspiInterrupt::TransferComplete);
        while self.is_busy() {}

        // todo: Fix this
        assert!(
            buf.len() <= 32,
            "Transactions larger than the QSPI FIFO are currently unsupported"
        );

        // Steps are equivalent to those listed in `write_indirect`.
        self.regs
            .dlr
            .write(|w| unsafe { w.dl().bits(buf.len() as u32 - 1) });
        self.regs
            .ccr
            .modify(|_, w| unsafe { w.fmode().bits(FunctionalMode::IndirectRead as u8) });
        self.regs
            .ar
            .modify(|_, w| unsafe { w.address().bits(addr) });

        // Check for underflow on the FIFO.
        if (self.regs.sr.read().flevel().bits() as usize) < buf.len() {
            return Err(QspiError::Underflow);
        }

        unsafe {
            for word in buf {
                *word = ptr::read_volatile(&self.regs.dr as *const _ as *const u8);
            }
        }

        // Wait for the peripheral to indicate it is no longer busy.
        while self.is_busy() {}

        Ok(())
    }

    // todo: write_indirect_dma fn.

    /// Read one word from memory in memory-mapped mode
    pub fn read_1_mem_mapped(&mut self, offset: isize) -> u32 {
        // todo: unsafe fn? word size?
        while self.is_busy() {}

        if self.regs.ccr.read().fmode().bits() != FunctionalMode::MemoryMapped as u8 {
            self.regs
                .ccr
                .modify(|_, w| unsafe { w.fmode().bits(FunctionalMode::MemoryMapped as u8) });
        }

        let addr = MEM_MAPPED_BASE_ADDR as *const u32; // as const what?
        unsafe { core::ptr::read(addr.offset(offset)) }
    }
}