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