1032 lines
32 KiB
Rust

use core::convert::Infallible;
use crate::clocks::Clocks;
use crate::enable_amba_peripheral_clock;
use crate::gpio::{
IoPeriph, IoPeriphPin, Mio10, Mio11, Mio12, Mio13, Mio14, Mio15, Mio28, Mio29, Mio30, Mio31,
Mio32, Mio33, Mio34, Mio35, Mio36, Mio37, Mio38, Mio39, MioPin, MuxConf,
};
#[cfg(not(feature = "7z010-7z007s-clg225"))]
use crate::gpio::{
Mio16, Mio17, Mio18, Mio19, Mio20, Mio21, Mio22, Mio23, Mio24, Mio25, Mio26, Mio27, Mio40,
Mio41, Mio42, Mio43, Mio44, Mio45, Mio46, Mio47, Mio48, Mio49, Mio50, Mio51,
};
use crate::{clocks::IoClocks, slcr::Slcr, time::Hertz};
use arbitrary_int::{Number, u3, u4, u6};
use embedded_hal::delay::DelayNs;
pub use embedded_hal::spi::Mode;
use embedded_hal::spi::{MODE_0, MODE_1, MODE_2, MODE_3, SpiBus as _};
use zynq7000::slcr::reset::DualRefAndClockReset;
use zynq7000::spi::{
BaudDivSelect, DelayControl, FifoWrite, MmioSpi, SPI_0_BASE_ADDR, SPI_1_BASE_ADDR,
};
pub const FIFO_DEPTH: usize = 128;
pub const MODULE_ID: u32 = 0x90106;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpiId {
Spi0,
Spi1,
}
pub trait PsSpi {
fn reg_block(&self) -> MmioSpi<'static>;
fn id(&self) -> Option<SpiId>;
}
impl PsSpi for MmioSpi<'static> {
#[inline]
fn reg_block(&self) -> MmioSpi<'static> {
unsafe { self.clone() }
}
#[inline]
fn id(&self) -> Option<SpiId> {
let base_addr = unsafe { self.ptr() } as usize;
if base_addr == SPI_0_BASE_ADDR {
return Some(SpiId::Spi0);
} else if base_addr == SPI_1_BASE_ADDR {
return Some(SpiId::Spi1);
}
None
}
}
pub trait SckPin: IoPeriphPin {
const SPI: SpiId;
const GROUP: usize;
}
pub trait MosiPin: IoPeriphPin {
const SPI: SpiId;
const GROUP: usize;
}
pub trait MisoPin: IoPeriphPin {
const SPI: SpiId;
const GROUP: usize;
}
pub trait SsPin: IoPeriphPin {
const IDX: usize;
const SPI: SpiId;
const GROUP: usize;
}
pub const SPI_MUX_CONF: MuxConf = MuxConf::new_with_l3(u3::new(0b101));
macro_rules! impl_into_spi {
(($($Mio:ident),+)) => {
$(
impl<M: $crate::gpio::PinMode> MioPin<$Mio, M> {
/// Convert the pin into SPI pins by configuring the pin routing via the
/// MIO multiplexer bits. Also disables pull-ups for the pins.
pub fn into_spi(self) -> MioPin<$Mio, IoPeriph> {
self.into_io_periph(SPI_MUX_CONF, Some(false))
}
}
)+
};
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl_into_spi!((
Mio16, Mio21, Mio17, Mio18, Mio19, Mio20, Mio40, Mio45, Mio41, Mio42, Mio43, Mio44, Mio24,
Mio22, Mio23, Mio25, Mio26, Mio27, Mio48, Mio46, Mio47, Mio49, Mio50, Mio51
));
impl_into_spi!((
Mio28, Mio33, Mio29, Mio30, Mio31, Mio32, Mio12, Mio10, Mio11, Mio13, Mio14, Mio15, Mio36,
Mio34, Mio35, Mio37, Mio38, Mio39
));
// SPI0, choice 1
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl SckPin for MioPin<Mio16, IoPeriph> {
const SPI: SpiId = SpiId::Spi0;
const GROUP: usize = 0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl MosiPin for MioPin<Mio21, IoPeriph> {
const SPI: SpiId = SpiId::Spi0;
const GROUP: usize = 0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl MisoPin for MioPin<Mio17, IoPeriph> {
const SPI: SpiId = SpiId::Spi0;
const GROUP: usize = 0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl SsPin for MioPin<Mio18, IoPeriph> {
const SPI: SpiId = SpiId::Spi0;
const GROUP: usize = 0;
const IDX: usize = 0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl SsPin for MioPin<Mio19, IoPeriph> {
const SPI: SpiId = SpiId::Spi0;
const GROUP: usize = 0;
const IDX: usize = 1;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl SsPin for MioPin<Mio20, IoPeriph> {
const SPI: SpiId = SpiId::Spi0;
const GROUP: usize = 0;
const IDX: usize = 2;
}
// SPI0, choice 2
impl SckPin for MioPin<Mio28, IoPeriph> {
const SPI: SpiId = SpiId::Spi0;
const GROUP: usize = 1;
}
impl MosiPin for MioPin<Mio33, IoPeriph> {
const SPI: SpiId = SpiId::Spi0;
const GROUP: usize = 1;
}
impl MisoPin for MioPin<Mio29, IoPeriph> {
const SPI: SpiId = SpiId::Spi0;
const GROUP: usize = 1;
}
impl SsPin for MioPin<Mio30, IoPeriph> {
const SPI: SpiId = SpiId::Spi0;
const GROUP: usize = 1;
const IDX: usize = 0;
}
impl SsPin for MioPin<Mio31, IoPeriph> {
const SPI: SpiId = SpiId::Spi0;
const GROUP: usize = 1;
const IDX: usize = 1;
}
impl SsPin for MioPin<Mio32, IoPeriph> {
const SPI: SpiId = SpiId::Spi0;
const GROUP: usize = 1;
const IDX: usize = 2;
}
// SPI0, choice 3
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl SckPin for MioPin<Mio40, IoPeriph> {
const SPI: SpiId = SpiId::Spi0;
const GROUP: usize = 2;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl MosiPin for MioPin<Mio45, IoPeriph> {
const SPI: SpiId = SpiId::Spi0;
const GROUP: usize = 2;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl MisoPin for MioPin<Mio41, IoPeriph> {
const SPI: SpiId = SpiId::Spi0;
const GROUP: usize = 2;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl SsPin for MioPin<Mio42, IoPeriph> {
const SPI: SpiId = SpiId::Spi0;
const GROUP: usize = 2;
const IDX: usize = 0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl SsPin for MioPin<Mio43, IoPeriph> {
const SPI: SpiId = SpiId::Spi0;
const GROUP: usize = 2;
const IDX: usize = 1;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl SsPin for MioPin<Mio44, IoPeriph> {
const SPI: SpiId = SpiId::Spi0;
const GROUP: usize = 2;
const IDX: usize = 2;
}
// SPI1, choice 1
impl SckPin for MioPin<Mio12, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 0;
}
impl MosiPin for MioPin<Mio10, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 0;
}
impl MisoPin for MioPin<Mio11, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 0;
}
impl SsPin for MioPin<Mio13, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 0;
const IDX: usize = 0;
}
impl SsPin for MioPin<Mio14, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 0;
const IDX: usize = 1;
}
impl SsPin for MioPin<Mio15, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 0;
const IDX: usize = 2;
}
// SPI1, choice 2
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl SckPin for MioPin<Mio24, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 1;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl MosiPin for MioPin<Mio22, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 1;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl MisoPin for MioPin<Mio23, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 1;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl SsPin for MioPin<Mio25, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 1;
const IDX: usize = 0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl SsPin for MioPin<Mio26, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 1;
const IDX: usize = 1;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl SsPin for MioPin<Mio27, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 1;
const IDX: usize = 2;
}
// SPI1, choice 2
impl SckPin for MioPin<Mio36, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 2;
}
impl MosiPin for MioPin<Mio34, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 2;
}
impl MisoPin for MioPin<Mio35, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 2;
}
impl SsPin for MioPin<Mio37, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 2;
const IDX: usize = 0;
}
impl SsPin for MioPin<Mio38, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 2;
const IDX: usize = 1;
}
impl SsPin for MioPin<Mio39, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 2;
const IDX: usize = 2;
}
// SPI1, choice 3
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl SckPin for MioPin<Mio48, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 3;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl MosiPin for MioPin<Mio46, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 3;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl MisoPin for MioPin<Mio47, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 3;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl SsPin for MioPin<Mio49, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 3;
const IDX: usize = 0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl SsPin for MioPin<Mio50, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 3;
const IDX: usize = 1;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl SsPin for MioPin<Mio51, IoPeriph> {
const SPI: SpiId = SpiId::Spi1;
const GROUP: usize = 3;
const IDX: usize = 2;
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum ChipSelect {
Slave0,
Slave1,
Slave2,
Decoded { cs0: bool, cs1: bool, cs2: bool },
None,
}
impl ChipSelect {
pub const fn raw_reg(&self) -> u4 {
u4::new(match self {
ChipSelect::Slave0 => 0b0000,
ChipSelect::Slave1 => 0b0001,
ChipSelect::Slave2 => 0b0010,
ChipSelect::None => 0b1111,
ChipSelect::Decoded { cs0, cs1, cs2 } => {
(1 << 3) | (*cs0 as u8) | ((*cs1 as u8) << 1) | ((*cs2 as u8) << 2)
}
})
}
#[inline]
pub const fn cs_no_ext_decoding(&self, raw: u4) -> Option<ChipSelect> {
let val = raw.value();
if val == 0b1111 {
return Some(ChipSelect::None);
}
if val & 0b1 == 0 {
return Some(ChipSelect::Slave0);
} else if val & 0b11 == 0b01 {
return Some(ChipSelect::Slave1);
} else if val & 0b111 == 0b010 {
return Some(ChipSelect::Slave2);
}
None
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
/// Slave select configuration.
pub enum SlaveSelectConfig {
/// User must take care of controlling slave select lines as well as issuing a start command.
ManualWithManualStart = 0b11,
ManualAutoStart = 0b10,
/// Hardware slave select, but start needs to be issued manually.
AutoWithManualStart = 0b01,
/// Hardware slave select, auto serialiation if there is data in the TX FIFO.
#[default]
AutoWithAutoStart = 0b00,
}
pub struct Config {
baud_div: BaudDivSelect,
init_mode: Mode,
ss_config: SlaveSelectConfig,
with_ext_decoding: bool,
}
impl Config {
pub fn new(baud_div: BaudDivSelect, init_mode: Mode, ss_config: SlaveSelectConfig) -> Self {
Self {
baud_div,
init_mode,
ss_config,
with_ext_decoding: false,
}
}
pub fn enable_external_decoding(&mut self) {
self.with_ext_decoding = true;
}
}
/// Blocking Driver for the PS SPI peripheral in master mode.
pub struct Spi {
regs: zynq7000::spi::MmioSpi<'static>,
sclk: Hertz,
config: Config,
outstanding_rx: bool,
}
#[derive(Debug, thiserror::Error)]
#[error("invalid SPI ID")]
pub struct InvalidPsSpiError;
// TODO: Add and handle MUX config check.
#[derive(Debug, thiserror::Error)]
pub enum SpiConstructionError {
#[error("invalid SPI ID {0}")]
InvalidPsSpi(#[from] InvalidPsSpiError),
/// The specified pins are not compatible to the specified SPI peripheral.
#[error("pin invalid for SPI ID")]
PinInvalidForSpiId,
/// The specified pins are not from the same pin group.
#[error("pin group missmatch")]
GroupMissmatch,
#[error("invalid pin configuration for SPI")]
InvalidPinConf,
}
impl Spi {
pub fn new_no_hw_ss<Sck: SckPin, Mosi: MosiPin, Miso: MisoPin>(
spi: impl PsSpi,
clocks: &IoClocks,
config: Config,
spi_pins: (Sck, Mosi, Miso),
) -> Result<Self, SpiConstructionError> {
let spi_id = spi.id();
if spi_id.is_none() {
return Err(InvalidPsSpiError.into());
}
let spi_id = spi_id.unwrap();
if Sck::GROUP != Mosi::GROUP || Sck::GROUP != Miso::GROUP {
return Err(SpiConstructionError::GroupMissmatch);
}
if Sck::SPI != spi_id || Mosi::SPI != spi_id || Miso::SPI != spi_id {
return Err(SpiConstructionError::PinInvalidForSpiId);
}
if spi_pins.0.mux_conf() != SPI_MUX_CONF
|| spi_pins.0.mux_conf() != spi_pins.2.mux_conf()
|| spi_pins.1.mux_conf() != spi_pins.2.mux_conf()
{
return Err(SpiConstructionError::InvalidPinConf);
}
Ok(Self::new_generic_unchecked(
spi_id,
spi.reg_block(),
clocks,
config,
))
}
pub fn new_one_hw_cs<Sck: SckPin, Mosi: MosiPin, Miso: MisoPin, Ss: SsPin>(
spi: impl PsSpi,
clocks: &IoClocks,
config: Config,
spi_pins: (Sck, Mosi, Miso),
ss_pin: Ss,
) -> Result<Self, SpiConstructionError> {
let spi_id = spi.id();
if spi_id.is_none() {
return Err(InvalidPsSpiError.into());
}
let spi_id = spi_id.unwrap();
if Sck::GROUP != Mosi::GROUP || Sck::GROUP != Miso::GROUP || Sck::GROUP != Ss::GROUP {
return Err(SpiConstructionError::GroupMissmatch);
}
if Sck::SPI != spi_id || Mosi::SPI != spi_id || Miso::SPI != spi_id || Ss::SPI != spi_id {
return Err(SpiConstructionError::PinInvalidForSpiId);
}
if spi_pins.0.mux_conf() != SPI_MUX_CONF
|| spi_pins.0.mux_conf() != spi_pins.2.mux_conf()
|| spi_pins.1.mux_conf() != spi_pins.2.mux_conf()
|| ss_pin.mux_conf() != spi_pins.0.mux_conf()
{
return Err(SpiConstructionError::InvalidPinConf);
}
Ok(Self::new_generic_unchecked(
spi_id,
spi.reg_block(),
clocks,
config,
))
}
pub fn new_with_two_hw_cs<Sck: SckPin, Mosi: MosiPin, Miso: MisoPin, Ss0: SsPin, Ss1: SsPin>(
spi: impl PsSpi,
clocks: &IoClocks,
config: Config,
spi_pins: (Sck, Mosi, Miso),
ss_pins: (Ss0, Ss1),
) -> Result<Self, SpiConstructionError> {
let spi_id = spi.id();
if spi_id.is_none() {
return Err(InvalidPsSpiError.into());
}
let spi_id = spi_id.unwrap();
if Sck::GROUP != Mosi::GROUP
|| Sck::GROUP != Miso::GROUP
|| Sck::GROUP != Ss0::GROUP
|| Sck::GROUP != Ss1::GROUP
{
return Err(SpiConstructionError::GroupMissmatch);
}
if Sck::SPI != spi_id
|| Mosi::SPI != spi_id
|| Miso::SPI != spi_id
|| Ss0::SPI != spi_id
|| Ss1::SPI != spi_id
{
return Err(SpiConstructionError::PinInvalidForSpiId);
}
if spi_pins.0.mux_conf() != SPI_MUX_CONF
|| spi_pins.0.mux_conf() != spi_pins.2.mux_conf()
|| spi_pins.1.mux_conf() != spi_pins.2.mux_conf()
|| ss_pins.0.mux_conf() != spi_pins.0.mux_conf()
|| ss_pins.1.mux_conf() != spi_pins.0.mux_conf()
{
return Err(SpiConstructionError::InvalidPinConf);
}
Ok(Self::new_generic_unchecked(
spi_id,
spi.reg_block(),
clocks,
config,
))
}
pub fn new_with_three_hw_cs<
Sck: SckPin,
Mosi: MosiPin,
Miso: MisoPin,
Ss0: SsPin,
Ss1: SsPin,
Ss2: SsPin,
>(
spi: impl PsSpi,
clocks: &IoClocks,
config: Config,
spi_pins: (Sck, Mosi, Miso),
ss_pins: (Ss0, Ss1, Ss2),
) -> Result<Self, SpiConstructionError> {
let spi_id = spi.id();
if spi_id.is_none() {
return Err(InvalidPsSpiError.into());
}
let spi_id = spi_id.unwrap();
if Sck::GROUP != Mosi::GROUP
|| Sck::GROUP != Miso::GROUP
|| Sck::GROUP != Ss0::GROUP
|| Sck::GROUP != Ss1::GROUP
|| Sck::GROUP != Ss2::GROUP
{
return Err(SpiConstructionError::GroupMissmatch);
}
if Sck::SPI != spi_id
|| Mosi::SPI != spi_id
|| Miso::SPI != spi_id
|| Ss0::SPI != spi_id
|| Ss1::SPI != spi_id
|| Ss2::SPI != spi_id
{
return Err(SpiConstructionError::PinInvalidForSpiId);
}
if spi_pins.0.mux_conf() != SPI_MUX_CONF
|| spi_pins.0.mux_conf() != spi_pins.2.mux_conf()
|| spi_pins.1.mux_conf() != spi_pins.2.mux_conf()
|| ss_pins.0.mux_conf() != spi_pins.0.mux_conf()
|| ss_pins.1.mux_conf() != spi_pins.0.mux_conf()
|| ss_pins.2.mux_conf() != spi_pins.2.mux_conf()
{
return Err(SpiConstructionError::InvalidPinConf);
}
Ok(Self::new_generic_unchecked(
spi_id,
spi.reg_block(),
clocks,
config,
))
}
pub fn new_generic_unchecked(
id: SpiId,
mut regs: MmioSpi<'static>,
clocks: &IoClocks,
config: Config,
) -> Self {
let periph_sel = match id {
SpiId::Spi0 => crate::PeripheralSelect::Spi0,
SpiId::Spi1 => crate::PeripheralSelect::Spi1,
};
enable_amba_peripheral_clock(periph_sel);
reset(id);
regs.write_enable(0);
let (man_ss, man_start) = match config.ss_config {
SlaveSelectConfig::ManualWithManualStart => (true, true),
SlaveSelectConfig::ManualAutoStart => (true, false),
SlaveSelectConfig::AutoWithManualStart => (false, true),
SlaveSelectConfig::AutoWithAutoStart => (false, false),
};
let (cpol, cpha) = match config.init_mode {
MODE_0 => (false, false),
MODE_1 => (false, true),
MODE_2 => (true, false),
MODE_3 => (true, true),
};
regs.write_cr(
zynq7000::spi::Config::builder()
.with_modefail_gen_en(false)
.with_manual_start(false)
.with_manual_start_enable(man_start)
.with_manual_cs(man_ss)
.with_cs_raw(ChipSelect::None.raw_reg())
.with_peri_sel(config.with_ext_decoding)
.with_baud_rate_div(config.baud_div)
.with_cpha(cpha)
.with_cpol(cpol)
.with_master_ern(true)
.build(),
);
// Configures for polling mode by default: TX trigger by one will lead to the
// TX FIFO not full signal being set when the TX FIFO is emtpy.
regs.write_tx_trig(1);
// Same as TX.
regs.write_rx_trig(1);
regs.write_enable(1);
let sclk = clocks.spi_clk() / config.baud_div.div_value() as u32;
Self {
regs,
sclk,
config,
outstanding_rx: false,
}
}
/// Retrieve SCLK clock frequency currently configured for this SPI.
#[inline]
pub const fn sclk(&self) -> Hertz {
self.sclk
}
#[inline]
pub fn regs(&mut self) -> &mut MmioSpi<'static> {
&mut self.regs
}
/// Select the peripheral chip select line.
///
/// This needs to be done before starting a transfer to select the correct peripheral chip
/// select line.
///
/// The decoder bits logic is is based
/// [on online documentation](https://www.realdigital.org/doc/3eb4f7a05e5003f2e0e6858a27a679bb?utm_source=chatgpt.com)
/// because the TRM does not specify how decoding really works. This also only works if
/// the external decoding was enabled via the [Config::enable_external_decoding] option.
#[inline]
pub fn select_hw_cs(&mut self, chip_select: ChipSelect) {
self.regs.modify_cr(|mut val| {
val.set_cs_raw(chip_select.raw_reg());
val
});
}
/// Re-configures the mode register.
#[inline]
pub fn configure_mode(&mut self, mode: Mode) {
let (cpol, cpha) = match mode {
MODE_0 => (false, false),
MODE_1 => (false, true),
MODE_2 => (true, false),
MODE_3 => (true, true),
};
self.regs.modify_cr(|mut val| {
val.set_cpha(cpha);
val.set_cpha(cpol);
val
});
}
/// Re-configures the delay control register.
#[inline]
pub fn configure_delays(&mut self, config: DelayControl) {
self.regs.write_delay_control(config)
}
/// No peripheral slave selection.
#[inline]
pub fn no_hw_cs(&mut self) {
self.select_hw_cs(ChipSelect::None);
}
#[inline(always)]
pub fn write_fifo_unchecked(&mut self, data: u8) {
self.regs
.write_txd(FifoWrite::new_with_raw_value(data as u32));
}
#[inline(always)]
pub fn read_fifo_unchecked(&mut self) -> u8 {
self.regs.read_rxd().data()
}
#[inline]
pub fn issue_manual_start(&mut self) {
self.regs.modify_cr(|mut val| {
val.set_manual_start(true);
val
});
}
#[inline]
pub fn issue_manual_start_for_manual_cfg(&mut self) {
if self.config.ss_config == SlaveSelectConfig::AutoWithManualStart
|| self.config.ss_config == SlaveSelectConfig::ManualWithManualStart
{
self.issue_manual_start();
}
}
fn initial_fifo_fill(&mut self, words: &[u8]) -> usize {
let write_len = core::cmp::min(FIFO_DEPTH, words.len());
(0..write_len).for_each(|idx| {
self.write_fifo_unchecked(words[idx]);
});
write_len
}
fn prepare_generic_blocking_transfer(&mut self, words: &[u8]) -> usize {
// We want to ensure the FIFO is empty for a new transfer. This is the simpler
// implementation for now.
self.flush().unwrap();
// Write this to 1 in any case to allow polling, defensive programming.
self.regs.write_rx_trig(1);
// Fill the FIFO with initial data.
let written = self.initial_fifo_fill(words);
// We assume that the slave select configuration was already performed, but we take
// care of issuing a start if necessary.
self.issue_manual_start_for_manual_cfg();
written
}
}
impl embedded_hal::spi::ErrorType for Spi {
type Error = Infallible;
}
impl embedded_hal::spi::SpiBus for Spi {
fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
if words.is_empty() {
return Ok(());
}
// We want to ensure the FIFO is empty for a new transfer. This is the simpler
// implementation for now.
self.flush()?;
// Write this to 1 in any case to allow polling, defensive programming.
self.regs.write_rx_trig(1);
let mut write_idx = core::cmp::min(FIFO_DEPTH, words.len());
// Send dummy bytes.
(0..write_idx).for_each(|_| {
self.write_fifo_unchecked(0);
});
// We assume that the slave select configuration was already performed, but we take
// care of issuing a start if necessary.
self.issue_manual_start_for_manual_cfg();
let mut read_idx = 0;
while read_idx < words.len() {
let status = self.regs.read_sr();
if status.rx_not_empty() {
words[read_idx] = self.read_fifo_unchecked();
read_idx += 1;
}
// Continue pumping the FIFO if necesary and possible.
if write_idx < words.len() && !status.tx_full() {
self.write_fifo_unchecked(0);
write_idx += 1;
}
}
Ok(())
}
fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
if words.is_empty() {
return Ok(());
}
let mut written = self.prepare_generic_blocking_transfer(words);
let mut read_idx = 0;
while written < words.len() {
let status = self.regs.read_sr();
// We empty the FIFO to prevent it filling up completely, as long as we have to write
// bytes
if status.rx_not_empty() {
self.read_fifo_unchecked();
read_idx += 1;
}
if !status.tx_full() {
self.write_fifo_unchecked(words[written]);
written += 1;
}
}
// We exit once all bytes have been written, so some bytes to read might be outstanding.
// We use the FIFO trigger mechanism to determine when we can read all the remaining bytes.
self.regs.write_rx_trig((words.len() - read_idx) as u32);
self.outstanding_rx = true;
Ok(())
}
fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
if read.is_empty() {
return Ok(());
}
let mut write_idx = self.prepare_generic_blocking_transfer(write);
let mut read_idx = 0;
let mut writes_finished = write_idx == write.len();
let mut reads_finished = false;
while !writes_finished || !reads_finished {
let status = self.regs.read_sr();
if status.rx_not_empty() && !reads_finished {
read[read_idx] = self.read_fifo_unchecked();
read_idx += 1;
}
if !status.tx_full() && !writes_finished {
self.write_fifo_unchecked(write[write_idx]);
write_idx += 1;
}
writes_finished = write_idx == write.len();
reads_finished = read_idx == write.len();
}
Ok(())
}
fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
if words.is_empty() {
return Ok(());
}
let mut write_idx = self.prepare_generic_blocking_transfer(words);
let mut read_idx = 0;
let mut writes_finished = write_idx == words.len();
let mut reads_finished = false;
while !writes_finished || !reads_finished {
let status = self.regs.read_sr();
if status.rx_not_empty() && !reads_finished {
words[read_idx] = self.read_fifo_unchecked();
read_idx += 1;
}
if !status.tx_full() && !writes_finished {
self.write_fifo_unchecked(words[write_idx]);
write_idx += 1;
}
writes_finished = write_idx == words.len();
reads_finished = read_idx == words.len();
}
Ok(())
}
fn flush(&mut self) -> Result<(), Self::Error> {
if !self.outstanding_rx {
return Ok(());
}
let rx_trig = self.regs.read_rx_trig();
while !self.regs.read_sr().rx_not_empty() {}
(0..rx_trig).for_each(|_| {
self.regs.read_rxd();
});
self.regs.write_rx_trig(1);
self.outstanding_rx = false;
Ok(())
}
}
pub struct SpiWithHwCs<Delay: DelayNs> {
pub spi: Spi,
pub cs: ChipSelect,
pub delay: Delay,
}
impl<Delay: DelayNs> SpiWithHwCs<Delay> {
pub fn new(spi: Spi, cs: ChipSelect, delay: Delay) -> Self {
Self { spi, cs, delay }
}
pub fn release(self) -> Spi {
self.spi
}
}
impl<Delay: DelayNs> embedded_hal::spi::ErrorType for SpiWithHwCs<Delay> {
type Error = Infallible;
}
impl<Delay: DelayNs> embedded_hal::spi::SpiDevice for SpiWithHwCs<Delay> {
fn transaction(
&mut self,
operations: &mut [embedded_hal::spi::Operation<'_, u8>],
) -> Result<(), Self::Error> {
self.spi.select_hw_cs(self.cs);
for op in operations {
match op {
embedded_hal::spi::Operation::Read(items) => {
self.spi.read(items)?;
}
embedded_hal::spi::Operation::Write(items) => {
self.spi.write(items)?;
}
embedded_hal::spi::Operation::Transfer(read, write) => {
self.spi.transfer(read, write)?;
}
embedded_hal::spi::Operation::TransferInPlace(items) => {
self.spi.transfer_in_place(items)?;
}
embedded_hal::spi::Operation::DelayNs(delay) => {
self.delay.delay_ns(*delay);
}
}
}
self.spi.flush()?;
self.spi.no_hw_cs();
Ok(())
}
}
/// Reset the SPI peripheral using the SLCR reset register for SPI.
///
/// Please note that this function will interfere with an already configured
/// SPI instance.
#[inline]
pub fn reset(id: SpiId) {
let assert_reset = match id {
SpiId::Spi0 => DualRefAndClockReset::builder()
.with_periph1_ref_rst(false)
.with_periph0_ref_rst(true)
.with_periph1_cpu1x_rst(false)
.with_periph0_cpu1x_rst(true)
.build(),
SpiId::Spi1 => DualRefAndClockReset::builder()
.with_periph1_ref_rst(true)
.with_periph0_ref_rst(false)
.with_periph1_cpu1x_rst(true)
.with_periph0_cpu1x_rst(false)
.build(),
};
unsafe {
Slcr::with(|regs| {
regs.reset_ctrl().write_spi(assert_reset);
// Keep it in reset for some cycles.. The TMR just mentions some small delay,
// no idea what is meant with that.
for _ in 0..3 {
cortex_ar::asm::nop();
}
regs.reset_ctrl().write_spi(DualRefAndClockReset::DEFAULT);
});
}
}
/// Calculates the largest allowed SPI reference clock divisor.
///
/// The Zynq7000 SPI peripheral has the following requirement for the SPI reference clock:
/// It must be larger than the CPU 1X clock. Therefore, the divisor used to calculate the reference
/// clock has a maximum value, which can be calculated with this function.
///
/// [configure_spi_ref_clk] can be used to configure the SPI reference clock with the calculated
/// value.
pub fn calculate_largest_allowed_spi_ref_clk_divisor(clks: &Clocks) -> Option<u6> {
let mut slcr = unsafe { Slcr::steal() };
let spi_clk_ctrl = slcr.regs().clk_ctrl().read_spi_clk_ctrl();
let div = match spi_clk_ctrl.srcsel() {
zynq7000::slcr::clocks::SrcSelIo::IoPll | zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => {
clks.io_clocks().ref_clk() / clks.arm_clocks().cpu_1x_clk()
}
zynq7000::slcr::clocks::SrcSelIo::ArmPll => {
clks.arm_clocks().ref_clk() / clks.arm_clocks().cpu_1x_clk()
}
zynq7000::slcr::clocks::SrcSelIo::DdrPll => {
clks.ddr_clocks().ref_clk() / clks.arm_clocks().cpu_1x_clk()
}
};
if div > u6::MAX.value() as u32 {
return None;
}
Some(u6::new(div as u8))
}
pub fn configure_spi_ref_clk(clks: &mut Clocks, divisor: u6) {
let mut slcr = unsafe { Slcr::steal() };
let spi_clk_ctrl = slcr.regs().clk_ctrl().read_spi_clk_ctrl();
slcr.modify(|regs| {
regs.clk_ctrl().modify_spi_clk_ctrl(|mut val| {
val.set_divisor(divisor);
val
});
});
let new_clk = match spi_clk_ctrl.srcsel() {
zynq7000::slcr::clocks::SrcSelIo::IoPll | zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => {
clks.io_clocks().ref_clk() / divisor.value() as u32
}
zynq7000::slcr::clocks::SrcSelIo::ArmPll => {
clks.arm_clocks().ref_clk() / divisor.value() as u32
}
zynq7000::slcr::clocks::SrcSelIo::DdrPll => {
clks.ddr_clocks().ref_clk() / divisor.value() as u32
}
};
clks.io_clocks_mut().update_spi_clk(new_clk);
}