libs/kaptan-stm32f7: add SPI1 DMA route

This commit is contained in:
Onur Özkan committed 2026-07-19 22:40:04 +03:00
1 parent e917650602
commit 508ca433a6
1 file changed
+289
+289
View File
@@ -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()
});
}
}