use enumset::EnumSet;
use portable_atomic::Ordering;
use crate::{
RegisterToggle,
asynch::AtomicWaker,
dma::{
BurstConfig,
DmaChannel,
DmaRxChannel,
DmaRxInterrupt,
DmaTxChannel,
DmaTxInterrupt,
InterruptAccess,
RegisterAccess,
RxRegisterAccess,
TxRegisterAccess,
},
interrupt::InterruptHandler,
peripherals::Interrupt,
system::{Peripheral, PeripheralGuard},
};
#[doc(hidden)]
pub struct ChannelInfo {
pub(crate) peripheral_interrupt: Interrupt,
pub(crate) async_handler: InterruptHandler,
pub(crate) compatible_peripherals: &'static [u8],
}
pub(crate) struct ChannelState {
pub(crate) tx_waker: AtomicWaker,
pub(crate) rx_waker: AtomicWaker,
pub(crate) tx_async_flag: portable_atomic::AtomicBool,
pub(crate) rx_async_flag: portable_atomic::AtomicBool,
}
pub(super) type SpiRegisterBlock = crate::pac::spi2::RegisterBlock;
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SpiDmaRxChannel<'d>(pub(crate) SpiDmaChannel<'d>);
impl SpiDmaRxChannel<'_> {
fn regs(&self) -> &SpiRegisterBlock {
self.0.register_block()
}
}
impl crate::private::Sealed for SpiDmaRxChannel<'_> {}
impl DmaRxChannel for SpiDmaRxChannel<'_> {}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SpiDmaTxChannel<'d>(pub(crate) SpiDmaChannel<'d>);
impl SpiDmaTxChannel<'_> {
fn regs(&self) -> &SpiRegisterBlock {
self.0.register_block()
}
}
impl crate::private::Sealed for SpiDmaTxChannel<'_> {}
impl DmaTxChannel for SpiDmaTxChannel<'_> {}
impl RegisterAccess for SpiDmaTxChannel<'_> {
#[allow(private_interfaces)]
fn enable(&self) -> Option<PeripheralGuard> {
cfg_select! {
esp32 => {
let clock = Peripheral::SpiDma;
}
_ => {
let clock = match self.0 {
SpiDmaChannel(any::Inner::Spi2(_)) => Peripheral::Spi2Dma,
SpiDmaChannel(any::Inner::Spi3(_)) => Peripheral::Spi3Dma,
};
}
}
Some(PeripheralGuard::new_with(clock, enable_spi_dma))
}
fn reset(&self) {
self.regs().dma_conf().toggle(|w, bit| w.out_rst().bit(bit));
}
fn set_burst_mode(&self, burst_mode: BurstConfig) {
self.regs()
.dma_conf()
.modify(|_, w| w.out_data_burst_en().bit(burst_mode.is_burst_enabled()));
}
fn set_descr_burst_mode(&self, burst_mode: bool) {
self.regs()
.dma_conf()
.modify(|_, w| w.outdscr_burst_en().bit(burst_mode));
}
fn set_link_addr(&self, address: u32) {
self.regs()
.dma_out_link()
.modify(|_, w| unsafe { w.outlink_addr().bits(address) });
}
fn start(&self) {
self.regs()
.dma_out_link()
.modify(|_, w| w.outlink_start().set_bit());
}
fn stop(&self) {
self.regs()
.dma_out_link()
.modify(|_, w| w.outlink_stop().set_bit());
}
fn restart(&self) {
self.regs()
.dma_out_link()
.modify(|_, w| w.outlink_restart().set_bit());
}
fn set_check_owner(&self, check_owner: Option<bool>) {
if check_owner == Some(true) {
panic!("SPI DMA does not support checking descriptor ownership");
}
}
#[cfg(dma_ext_mem_configurable_block_size)]
fn set_ext_mem_block_size(&self, size: crate::dma::DmaExtMemBKSize) {
self.regs()
.dma_conf()
.modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) });
}
#[cfg(dma_can_access_psram)]
fn can_access_psram(&self) -> bool {
matches!(self.0, SpiDmaChannel(any::Inner::Spi2(_)))
}
fn compatible_peripherals(&self) -> &[u8] {
self.0.info().compatible_peripherals
}
}
impl TxRegisterAccess for SpiDmaTxChannel<'_> {
fn is_fifo_empty(&self) -> bool {
cfg_select! {
esp32 => self.regs().dma_rstatus().read().dma_out_status().bits() & 0x80000000 != 0,
_ => self
.regs()
.dma_outstatus()
.read()
.dma_outfifo_empty()
.bit_is_set(),
}
}
fn set_auto_write_back(&self, enable: bool) {
assert!(!enable);
}
fn last_dscr_address(&self) -> usize {
self.regs()
.out_eof_des_addr()
.read()
.dma_out_eof_des_addr()
.bits() as usize
}
fn peripheral_interrupt(&self) -> Option<Interrupt> {
None
}
fn async_handler(&self) -> Option<InterruptHandler> {
None
}
}
impl InterruptAccess<DmaTxInterrupt> for SpiDmaTxChannel<'_> {
fn enable_listen(&self, interrupts: EnumSet<DmaTxInterrupt>, enable: bool) {
self.regs().dma_int_ena().modify(|_, w| {
for interrupt in interrupts {
match interrupt {
DmaTxInterrupt::TotalEof => w.out_total_eof().bit(enable),
DmaTxInterrupt::DescriptorError => w.outlink_dscr_error().bit(enable),
DmaTxInterrupt::Eof => w.out_eof().bit(enable),
DmaTxInterrupt::Done => w.out_done().bit(enable),
};
}
w
});
}
fn is_listening(&self) -> EnumSet<DmaTxInterrupt> {
let mut result = EnumSet::new();
let int_ena = self.regs().dma_int_ena().read();
if int_ena.out_total_eof().bit_is_set() {
result |= DmaTxInterrupt::TotalEof;
}
if int_ena.outlink_dscr_error().bit_is_set() {
result |= DmaTxInterrupt::DescriptorError;
}
if int_ena.out_eof().bit_is_set() {
result |= DmaTxInterrupt::Eof;
}
if int_ena.out_done().bit_is_set() {
result |= DmaTxInterrupt::Done;
}
result
}
fn clear(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
self.regs().dma_int_clr().write(|w| {
for interrupt in interrupts.into() {
match interrupt {
DmaTxInterrupt::TotalEof => w.out_total_eof().clear_bit_by_one(),
DmaTxInterrupt::DescriptorError => w.outlink_dscr_error().clear_bit_by_one(),
DmaTxInterrupt::Eof => w.out_eof().clear_bit_by_one(),
DmaTxInterrupt::Done => w.out_done().clear_bit_by_one(),
};
}
w
});
}
fn pending_interrupts(&self) -> EnumSet<DmaTxInterrupt> {
let mut result = EnumSet::new();
let int_raw = self.regs().dma_int_raw().read();
if int_raw.out_total_eof().bit_is_set() {
result |= DmaTxInterrupt::TotalEof;
}
if int_raw.outlink_dscr_error().bit_is_set() {
result |= DmaTxInterrupt::DescriptorError;
}
if int_raw.out_eof().bit_is_set() {
result |= DmaTxInterrupt::Eof;
}
if int_raw.out_done().bit_is_set() {
result |= DmaTxInterrupt::Done;
}
result
}
fn waker(&self) -> &'static AtomicWaker {
&self.0.state().tx_waker
}
fn is_async(&self) -> bool {
self.0.state().tx_async_flag.load(Ordering::Acquire)
}
fn set_async(&self, is_async: bool) {
self.0
.state()
.tx_async_flag
.store(is_async, Ordering::Release);
}
}
impl RegisterAccess for SpiDmaRxChannel<'_> {
#[allow(private_interfaces)]
fn enable(&self) -> Option<PeripheralGuard> {
cfg_select! {
esp32 => {
let clock = Peripheral::SpiDma;
}
_ => {
let clock = match self.0 {
SpiDmaChannel(any::Inner::Spi2(_)) => Peripheral::Spi2Dma,
SpiDmaChannel(any::Inner::Spi3(_)) => Peripheral::Spi3Dma,
};
}
}
Some(PeripheralGuard::new_with(clock, enable_spi_dma))
}
fn reset(&self) {
self.regs().dma_conf().toggle(|w, bit| w.in_rst().bit(bit));
}
fn set_burst_mode(&self, _burst_mode: BurstConfig) {}
fn set_descr_burst_mode(&self, burst_mode: bool) {
self.regs()
.dma_conf()
.modify(|_, w| w.indscr_burst_en().bit(burst_mode));
}
fn set_link_addr(&self, address: u32) {
self.regs()
.dma_in_link()
.modify(|_, w| unsafe { w.inlink_addr().bits(address) });
}
fn start(&self) {
self.regs()
.dma_in_link()
.modify(|_, w| w.inlink_start().set_bit());
}
fn stop(&self) {
self.regs()
.dma_in_link()
.modify(|_, w| w.inlink_stop().set_bit());
}
fn restart(&self) {
self.regs()
.dma_in_link()
.modify(|_, w| w.inlink_restart().set_bit());
}
fn set_check_owner(&self, check_owner: Option<bool>) {
if check_owner == Some(true) {
panic!("SPI DMA does not support checking descriptor ownership");
}
}
#[cfg(dma_ext_mem_configurable_block_size)]
fn set_ext_mem_block_size(&self, size: crate::dma::DmaExtMemBKSize) {
self.regs()
.dma_conf()
.modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) });
}
#[cfg(dma_can_access_psram)]
fn can_access_psram(&self) -> bool {
matches!(self.0, SpiDmaChannel(any::Inner::Spi2(_)))
}
fn compatible_peripherals(&self) -> &[u8] {
self.0.info().compatible_peripherals
}
}
impl RxRegisterAccess for SpiDmaRxChannel<'_> {
#[cfg(dma_supports_mem2mem)]
fn set_mem2mem_mode(&self, en: bool) {
self.regs()
.dma_conf()
.modify(|_, w| w.mem_trans_en().bit(en));
}
fn peripheral_interrupt(&self) -> Option<Interrupt> {
Some(self.0.info().peripheral_interrupt)
}
fn async_handler(&self) -> Option<InterruptHandler> {
Some(self.0.info().async_handler)
}
}
impl InterruptAccess<DmaRxInterrupt> for SpiDmaRxChannel<'_> {
fn enable_listen(&self, interrupts: EnumSet<DmaRxInterrupt>, enable: bool) {
self.regs().dma_int_ena().modify(|_, w| {
for interrupt in interrupts {
match interrupt {
DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().bit(enable),
DmaRxInterrupt::ErrorEof => w.in_err_eof().bit(enable),
DmaRxInterrupt::DescriptorError => w.inlink_dscr_error().bit(enable),
DmaRxInterrupt::DescriptorEmpty => w.inlink_dscr_empty().bit(enable),
DmaRxInterrupt::Done => w.in_done().bit(enable),
};
}
w
});
}
fn is_listening(&self) -> EnumSet<DmaRxInterrupt> {
let mut result = EnumSet::new();
let int_ena = self.regs().dma_int_ena().read();
if int_ena.inlink_dscr_error().bit_is_set() {
result |= DmaRxInterrupt::DescriptorError;
}
if int_ena.inlink_dscr_empty().bit_is_set() {
result |= DmaRxInterrupt::DescriptorEmpty;
}
if int_ena.in_suc_eof().bit_is_set() {
result |= DmaRxInterrupt::SuccessfulEof;
}
if int_ena.in_err_eof().bit_is_set() {
result |= DmaRxInterrupt::ErrorEof;
}
if int_ena.in_done().bit_is_set() {
result |= DmaRxInterrupt::Done;
}
result
}
fn clear(&self, interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
self.regs().dma_int_clr().modify(|_, w| {
for interrupt in interrupts.into() {
match interrupt {
DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().clear_bit_by_one(),
DmaRxInterrupt::ErrorEof => w.in_err_eof().clear_bit_by_one(),
DmaRxInterrupt::DescriptorError => w.inlink_dscr_error().clear_bit_by_one(),
DmaRxInterrupt::DescriptorEmpty => w.inlink_dscr_empty().clear_bit_by_one(),
DmaRxInterrupt::Done => w.in_done().clear_bit_by_one(),
};
}
w
});
}
fn pending_interrupts(&self) -> EnumSet<DmaRxInterrupt> {
let mut result = EnumSet::new();
let int_raw = self.regs().dma_int_raw().read();
if int_raw.inlink_dscr_error().bit_is_set() {
result |= DmaRxInterrupt::DescriptorError;
}
if int_raw.inlink_dscr_empty().bit_is_set() {
result |= DmaRxInterrupt::DescriptorEmpty;
}
if int_raw.in_suc_eof().bit_is_set() {
result |= DmaRxInterrupt::SuccessfulEof;
}
if int_raw.in_err_eof().bit_is_set() {
result |= DmaRxInterrupt::ErrorEof;
}
if int_raw.in_done().bit_is_set() {
result |= DmaRxInterrupt::Done;
}
result
}
fn waker(&self) -> &'static AtomicWaker {
&self.0.state().rx_waker
}
fn is_async(&self) -> bool {
self.0.state().rx_async_flag.load(Ordering::Relaxed)
}
fn set_async(&self, is_async: bool) {
self.0
.state()
.rx_async_flag
.store(is_async, Ordering::Relaxed);
}
}
crate::any_peripheral! {
pub peripheral SpiDmaChannel<'d> {
Spi2(DMA_SPI2<'d>),
Spi3(DMA_SPI3<'d>),
}
}
impl<'d> DmaChannel for SpiDmaChannel<'d> {
type Rx = SpiDmaRxChannel<'d>;
type Tx = SpiDmaTxChannel<'d>;
unsafe fn split_internal(self, _: crate::private::Internal) -> (Self::Rx, Self::Tx) {
(
SpiDmaRxChannel(unsafe { self.clone_unchecked() }),
SpiDmaTxChannel(self),
)
}
}
impl SpiDmaChannel<'_> {
delegate::delegate! {
to match &self.0 {
any::Inner::Spi2(channel) => channel,
any::Inner::Spi3(channel) => channel,
} {
fn register_block(&self) -> &SpiRegisterBlock;
fn info(&self) -> &'static ChannelInfo;
fn state(&self) -> &'static ChannelState;
}
}
}
impl<'d> From<SpiDmaChannel<'d>> for SpiDmaRxChannel<'d> {
fn from(this: SpiDmaChannel<'d>) -> SpiDmaRxChannel<'d> {
SpiDmaRxChannel(this)
}
}
impl<'d> From<SpiDmaChannel<'d>> for SpiDmaTxChannel<'d> {
fn from(this: SpiDmaChannel<'d>) -> SpiDmaTxChannel<'d> {
SpiDmaTxChannel(this)
}
}
for_each_dma_channel_peri_pair! {
("SPI_DMA", $dma_peri:ident, $peri:ident) => {
use $crate::peripherals::$dma_peri;
impl $dma_peri<'_> {
pub(super) fn info(&self) -> &'static ChannelInfo {
#[crate::handler(priority = crate::interrupt::Priority::max())]
fn interrupt_handler() {
crate::dma::asynch::handle_in_interrupt::<$dma_peri<'static>>();
crate::dma::asynch::handle_out_interrupt::<$dma_peri<'static>>();
}
static INFO: ChannelInfo = ChannelInfo {
peripheral_interrupt: paste::paste! { Interrupt::[<$peri _DMA>] },
async_handler: interrupt_handler,
compatible_peripherals: &[crate::dma::DmaPeripheral::$peri.0],
};
&INFO
}
pub(super) fn state(&self) -> &'static ChannelState {
static STATE: ChannelState = ChannelState {
tx_waker: AtomicWaker::new(),
rx_waker: AtomicWaker::new(),
tx_async_flag: portable_atomic::AtomicBool::new(false),
rx_async_flag: portable_atomic::AtomicBool::new(false),
};
&STATE
}
}
crate::dma::impl_channel_common!(SpiDma, $dma_peri);
};
}
pub(super) fn enable_spi_dma() {
#[cfg(esp32)]
{
use crate::peripherals::DPORT;
DPORT::regs().spi_dma_chan_sel().modify(|_, w| unsafe {
w.spi2_dma_chan_sel().bits(1);
w.spi3_dma_chan_sel().bits(2)
});
}
}