libs/kaptan-stm32f7: add SPI1 DMA route
This commit is contained in:
1 parent
e917650602
commit
508ca433a6
1 file changed
+289
@@ -0,0 +1,289 @@
|
||||
//! STM32F722 SPI1 full-duplex DMA controller.
|
||||
//!
|
||||
//! SPI1 uses DMA2 Stream0/Channel3 for receive and DMA2 Stream3/Channel3 for
|
||||
//! transmit. The receive terminal event is the transaction boundary because it
|
||||
//! is generated after the last serial frame; transmit completion can occur
|
||||
//! while the final serial clock is still in progress.
|
||||
|
||||
use kaptan_spi::SpiCompletion;
|
||||
use stm32f7::stm32f722;
|
||||
|
||||
const MAX_TRANSFER_LENGTH: usize = u16::MAX as usize;
|
||||
|
||||
/// Failure while submitting one SPI1 DMA transaction.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Spi1Error {
|
||||
/// The transaction is malformed or exceeds the DMA counter range.
|
||||
InvalidLength,
|
||||
/// A previous DMA transaction still owns SPI1.
|
||||
Busy,
|
||||
}
|
||||
|
||||
/// Owns SPI1 and its fixed full-duplex DMA2 request route.
|
||||
pub struct Spi1 {
|
||||
peripheral: stm32f722::SPI1,
|
||||
controller: stm32f722::DMA2,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
impl Spi1 {
|
||||
/// Configures SPI1 mode 0 master operation at APB2/64 and reserves DMA2.
|
||||
pub fn new(
|
||||
rcc: &stm32f722::RCC,
|
||||
peripheral: stm32f722::SPI1,
|
||||
controller: stm32f722::DMA2,
|
||||
) -> Self {
|
||||
rcc.apb2rstr().modify(|_, write| write.spi1rst().set_bit());
|
||||
rcc.apb2rstr()
|
||||
.modify(|_, write| write.spi1rst().clear_bit());
|
||||
rcc.apb2enr().modify(|_, write| write.spi1en().set_bit());
|
||||
rcc.ahb1enr().modify(|_, write| write.dma2en().set_bit());
|
||||
|
||||
peripheral.cr1().write(|write| {
|
||||
write
|
||||
.mstr()
|
||||
.master()
|
||||
.br()
|
||||
.div64()
|
||||
.ssm()
|
||||
.set_bit()
|
||||
.ssi()
|
||||
.set_bit()
|
||||
});
|
||||
peripheral
|
||||
.cr2()
|
||||
.write(|write| write.ds().eight_bit().frxth().set_bit());
|
||||
peripheral.cr1().modify(|_, write| write.spe().set_bit());
|
||||
|
||||
Self {
|
||||
peripheral,
|
||||
controller,
|
||||
active: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts a full-duplex DMA transaction without polling a peripheral flag.
|
||||
pub fn start_transfer(&mut self, transmit: &[u8], receive: &mut [u8]) -> Result<(), Spi1Error> {
|
||||
if transmit.is_empty()
|
||||
|| transmit.len() != receive.len()
|
||||
|| transmit.len() > MAX_TRANSFER_LENGTH
|
||||
{
|
||||
return Err(Spi1Error::InvalidLength);
|
||||
}
|
||||
if self.active
|
||||
|| self.rx_stream().cr().read().en().is_enabled()
|
||||
|| self.tx_stream().cr().read().en().is_enabled()
|
||||
{
|
||||
return Err(Spi1Error::Busy);
|
||||
}
|
||||
|
||||
self.clear_rx_flags();
|
||||
self.clear_tx_flags();
|
||||
self.configure_rx(receive);
|
||||
self.configure_tx(transmit);
|
||||
self.rx_stream()
|
||||
.cr()
|
||||
.modify(|_, write| write.en().enabled());
|
||||
self.tx_stream()
|
||||
.cr()
|
||||
.modify(|_, write| write.en().enabled());
|
||||
self.peripheral
|
||||
.cr2()
|
||||
.modify(|_, write| write.rxdmaen().set_bit().txdmaen().set_bit());
|
||||
self.active = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Releases controller-side ownership after a DMA terminal event.
|
||||
pub fn complete_transfer(&mut self, _: SpiCompletion) {
|
||||
self.peripheral
|
||||
.cr2()
|
||||
.modify(|_, write| write.rxdmaen().clear_bit().txdmaen().clear_bit());
|
||||
self.active = false;
|
||||
}
|
||||
|
||||
/// Acknowledges the RX terminal IRQ. Invoke only from DMA2 Stream0.
|
||||
pub fn acknowledge_rx_interrupt() -> SpiCompletion {
|
||||
// SAFETY: DMA2 is initialized once by `Spi1::new`; this ISR touches
|
||||
// only Stream0's status and clear registers, never worker state.
|
||||
let controller = unsafe { &*stm32f722::DMA2::ptr() };
|
||||
let status = controller.lisr().read();
|
||||
let fault =
|
||||
status.teif0().is_error() || status.dmeif0().is_error() || status.feif0().is_error();
|
||||
let complete = status.tcif0().is_complete();
|
||||
controller.lifcr().write(|write| {
|
||||
write
|
||||
.cfeif0()
|
||||
.clear()
|
||||
.cdmeif0()
|
||||
.clear()
|
||||
.cteif0()
|
||||
.clear()
|
||||
.chtif0()
|
||||
.clear()
|
||||
.ctcif0()
|
||||
.clear()
|
||||
});
|
||||
if fault {
|
||||
SpiCompletion::TransferFault
|
||||
} else if complete {
|
||||
SpiCompletion::Complete
|
||||
} else {
|
||||
SpiCompletion::NotPending
|
||||
}
|
||||
}
|
||||
|
||||
/// Acknowledges TX errors; TX completion alone is not a transaction event.
|
||||
pub fn acknowledge_tx_interrupt() -> SpiCompletion {
|
||||
// SAFETY: DMA2 Stream3 is initialized once by `Spi1::new`; this ISR
|
||||
// touches only Stream3's status and clear registers.
|
||||
let controller = unsafe { &*stm32f722::DMA2::ptr() };
|
||||
let status = controller.lisr().read();
|
||||
let fault =
|
||||
status.teif3().is_error() || status.dmeif3().is_error() || status.feif3().is_error();
|
||||
controller.lifcr().write(|write| {
|
||||
write
|
||||
.cfeif3()
|
||||
.clear()
|
||||
.cdmeif3()
|
||||
.clear()
|
||||
.cteif3()
|
||||
.clear()
|
||||
.chtif3()
|
||||
.clear()
|
||||
.ctcif3()
|
||||
.clear()
|
||||
});
|
||||
if fault {
|
||||
SpiCompletion::TransferFault
|
||||
} else {
|
||||
SpiCompletion::NotPending
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_rx(&self, receive: &mut [u8]) {
|
||||
self.rx_stream().cr().write(|write| {
|
||||
// SAFETY: Channel 3 is SPI1_RX on DMA2 Stream0; checked slice
|
||||
// length fits NDTR, and all values map directly to documented bits.
|
||||
unsafe {
|
||||
write
|
||||
.chsel()
|
||||
.bits(3)
|
||||
.dir()
|
||||
.peripheral_to_memory()
|
||||
.pl()
|
||||
.very_high()
|
||||
.minc()
|
||||
.incremented()
|
||||
.pinc()
|
||||
.fixed()
|
||||
.msize()
|
||||
.bits8()
|
||||
.psize()
|
||||
.bits8()
|
||||
.circ()
|
||||
.disabled()
|
||||
.tcie()
|
||||
.enabled()
|
||||
.teie()
|
||||
.enabled()
|
||||
.dmeie()
|
||||
.enabled()
|
||||
}
|
||||
});
|
||||
self.rx_stream()
|
||||
.ndtr()
|
||||
// SAFETY: `start_transfer` bounds the length to the 16-bit NDTR field.
|
||||
.write(|write| unsafe { write.bits(receive.len() as u32) });
|
||||
self.rx_stream()
|
||||
.par()
|
||||
// SAFETY: `DR` is the memory-mapped SPI1 DMA peripheral endpoint.
|
||||
.write(|write| unsafe { write.bits(self.peripheral.dr().as_ptr() as u32) });
|
||||
self.rx_stream()
|
||||
.m0ar()
|
||||
// SAFETY: the caller retains `receive` until terminal completion.
|
||||
.write(|write| unsafe { write.bits(receive.as_mut_ptr() as u32) });
|
||||
}
|
||||
|
||||
fn configure_tx(&self, transmit: &[u8]) {
|
||||
self.tx_stream().cr().write(|write| {
|
||||
// SAFETY: Channel 3 is SPI1_TX on DMA2 Stream3; checked slice
|
||||
// length fits NDTR, and all values map directly to documented bits.
|
||||
unsafe {
|
||||
write
|
||||
.chsel()
|
||||
.bits(3)
|
||||
.dir()
|
||||
.memory_to_peripheral()
|
||||
.pl()
|
||||
.very_high()
|
||||
.minc()
|
||||
.incremented()
|
||||
.pinc()
|
||||
.fixed()
|
||||
.msize()
|
||||
.bits8()
|
||||
.psize()
|
||||
.bits8()
|
||||
.circ()
|
||||
.disabled()
|
||||
.teie()
|
||||
.enabled()
|
||||
.dmeie()
|
||||
.enabled()
|
||||
}
|
||||
});
|
||||
self.tx_stream()
|
||||
.ndtr()
|
||||
// SAFETY: `start_transfer` bounds the length to the 16-bit NDTR field.
|
||||
.write(|write| unsafe { write.bits(transmit.len() as u32) });
|
||||
self.tx_stream()
|
||||
.par()
|
||||
// SAFETY: `DR` is the memory-mapped SPI1 DMA peripheral endpoint.
|
||||
.write(|write| unsafe { write.bits(self.peripheral.dr().as_ptr() as u32) });
|
||||
self.tx_stream()
|
||||
.m0ar()
|
||||
// SAFETY: the caller retains `transmit` until terminal completion.
|
||||
.write(|write| unsafe { write.bits(transmit.as_ptr() as u32) });
|
||||
}
|
||||
|
||||
fn rx_stream(&self) -> &stm32f722::dma2::ST {
|
||||
self.controller.st(0)
|
||||
}
|
||||
|
||||
fn tx_stream(&self) -> &stm32f722::dma2::ST {
|
||||
self.controller.st(3)
|
||||
}
|
||||
|
||||
fn clear_rx_flags(&self) {
|
||||
self.controller.lifcr().write(|write| {
|
||||
write
|
||||
.cfeif0()
|
||||
.clear()
|
||||
.cdmeif0()
|
||||
.clear()
|
||||
.cteif0()
|
||||
.clear()
|
||||
.chtif0()
|
||||
.clear()
|
||||
.ctcif0()
|
||||
.clear()
|
||||
});
|
||||
}
|
||||
|
||||
fn clear_tx_flags(&self) {
|
||||
self.controller.lifcr().write(|write| {
|
||||
write
|
||||
.cfeif3()
|
||||
.clear()
|
||||
.cdmeif3()
|
||||
.clear()
|
||||
.cteif3()
|
||||
.clear()
|
||||
.chtif3()
|
||||
.clear()
|
||||
.ctcif3()
|
||||
.clear()
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user