Split DMA RegisterAccess trait into RX/TX (#2249)
* Reduce macro usage in PDMA * Split DMA RegisterAccess trait into RX/TX * Move set_isr to Ext trait --------- Co-authored-by: Dominic Fischer <git@dominicfischer.me>
This commit is contained in:
parent
487de0ff22
commit
f1bedbe3dc
@ -21,11 +21,17 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct Channel<const N: u8> {}
|
#[doc(hidden)]
|
||||||
|
pub struct ChannelTxImpl<const N: u8> {}
|
||||||
|
|
||||||
impl<const N: u8> crate::private::Sealed for Channel<N> {}
|
use embassy_sync::waitqueue::AtomicWaker;
|
||||||
|
|
||||||
impl<const N: u8> Channel<N> {
|
static TX_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT];
|
||||||
|
static RX_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT];
|
||||||
|
|
||||||
|
impl<const N: u8> crate::private::Sealed for ChannelTxImpl<N> {}
|
||||||
|
|
||||||
|
impl<const N: u8> ChannelTxImpl<N> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn ch() -> &'static crate::peripherals::dma::ch::CH {
|
fn ch() -> &'static crate::peripherals::dma::ch::CH {
|
||||||
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
||||||
@ -34,74 +40,77 @@ impl<const N: u8> Channel<N> {
|
|||||||
|
|
||||||
#[cfg(any(esp32c2, esp32c3))]
|
#[cfg(any(esp32c2, esp32c3))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn in_int() -> &'static crate::peripherals::dma::int_ch::INT_CH {
|
fn int() -> &'static crate::peripherals::dma::int_ch::INT_CH {
|
||||||
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
||||||
dma.int_ch(N as usize)
|
dma.int_ch(N as usize)
|
||||||
}
|
}
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[cfg(any(esp32c6, esp32h2))]
|
#[cfg(any(esp32c6, esp32h2))]
|
||||||
fn in_int() -> &'static crate::peripherals::dma::in_int_ch::IN_INT_CH {
|
fn int() -> &'static crate::peripherals::dma::out_int_ch::OUT_INT_CH {
|
||||||
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
|
||||||
dma.in_int_ch(N as usize)
|
|
||||||
}
|
|
||||||
#[cfg(esp32s3)]
|
|
||||||
#[inline(always)]
|
|
||||||
fn in_int() -> &'static crate::peripherals::dma::ch::in_int::IN_INT {
|
|
||||||
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
|
||||||
dma.ch(N as usize).in_int()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(esp32c2, esp32c3))]
|
|
||||||
#[inline(always)]
|
|
||||||
fn out_int() -> &'static crate::peripherals::dma::int_ch::INT_CH {
|
|
||||||
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
|
||||||
dma.int_ch(N as usize)
|
|
||||||
}
|
|
||||||
#[inline(always)]
|
|
||||||
#[cfg(any(esp32c6, esp32h2))]
|
|
||||||
fn out_int() -> &'static crate::peripherals::dma::out_int_ch::OUT_INT_CH {
|
|
||||||
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
||||||
dma.out_int_ch(N as usize)
|
dma.out_int_ch(N as usize)
|
||||||
}
|
}
|
||||||
#[cfg(esp32s3)]
|
#[cfg(esp32s3)]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn out_int() -> &'static crate::peripherals::dma::ch::out_int::OUT_INT {
|
fn int() -> &'static crate::peripherals::dma::ch::out_int::OUT_INT {
|
||||||
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
||||||
dma.ch(N as usize).out_int()
|
dma.ch(N as usize).out_int()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: u8> RegisterAccess for Channel<N> {
|
impl<const N: u8> RegisterAccess for ChannelTxImpl<N> {
|
||||||
#[cfg(gdma)]
|
fn reset(&self) {
|
||||||
fn set_mem2mem_mode(value: bool) {
|
let conf0 = Self::ch().out_conf0();
|
||||||
Self::ch()
|
conf0.modify(|_, w| w.out_rst().set_bit());
|
||||||
.in_conf0()
|
conf0.modify(|_, w| w.out_rst().clear_bit());
|
||||||
.modify(|_, w| w.mem_trans_en().bit(value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(esp32s3)]
|
fn set_burst_mode(&self, burst_mode: bool) {
|
||||||
fn set_out_ext_mem_block_size(size: DmaExtMemBKSize) {
|
|
||||||
Self::ch()
|
|
||||||
.out_conf1()
|
|
||||||
.modify(|_, w| unsafe { w.out_ext_mem_bk_size().bits(size as u8) });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_out_burstmode(burst_mode: bool) {
|
|
||||||
Self::ch().out_conf0().modify(|_, w| {
|
Self::ch().out_conf0().modify(|_, w| {
|
||||||
w.out_data_burst_en().bit(burst_mode);
|
w.out_data_burst_en().bit(burst_mode);
|
||||||
w.outdscr_burst_en().bit(burst_mode)
|
w.outdscr_burst_en().bit(burst_mode)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_out_priority(priority: DmaPriority) {
|
fn set_priority(&self, priority: DmaPriority) {
|
||||||
Self::ch()
|
Self::ch()
|
||||||
.out_pri()
|
.out_pri()
|
||||||
.write(|w| unsafe { w.tx_pri().bits(priority as u8) });
|
.write(|w| unsafe { w.tx_pri().bits(priority as u8) });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_out_interrupts() {
|
fn set_peripheral(&self, peripheral: u8) {
|
||||||
|
Self::ch()
|
||||||
|
.out_peri_sel()
|
||||||
|
.modify(|_, w| unsafe { w.peri_out_sel().bits(peripheral) });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_link_addr(&self, address: u32) {
|
||||||
|
Self::ch()
|
||||||
|
.out_link()
|
||||||
|
.modify(|_, w| unsafe { w.outlink_addr().bits(address) });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(&self) {
|
||||||
|
Self::ch()
|
||||||
|
.out_link()
|
||||||
|
.modify(|_, w| w.outlink_start().set_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&self) {
|
||||||
|
Self::ch()
|
||||||
|
.out_link()
|
||||||
|
.modify(|_, w| w.outlink_stop().set_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restart(&self) {
|
||||||
|
Self::ch()
|
||||||
|
.out_link()
|
||||||
|
.modify(|_, w| w.outlink_restart().set_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_interrupts(&self) {
|
||||||
#[cfg(not(esp32s3))]
|
#[cfg(not(esp32s3))]
|
||||||
Self::out_int().clr().write(|w| {
|
Self::int().clr().write(|w| {
|
||||||
w.out_eof().clear_bit_by_one();
|
w.out_eof().clear_bit_by_one();
|
||||||
w.out_dscr_err().clear_bit_by_one();
|
w.out_dscr_err().clear_bit_by_one();
|
||||||
w.out_done().clear_bit_by_one();
|
w.out_done().clear_bit_by_one();
|
||||||
@ -111,7 +120,7 @@ impl<const N: u8> RegisterAccess for Channel<N> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(esp32s3)]
|
#[cfg(esp32s3)]
|
||||||
Self::out_int().clr().write(|w| {
|
Self::int().clr().write(|w| {
|
||||||
w.out_eof().clear_bit_by_one();
|
w.out_eof().clear_bit_by_one();
|
||||||
w.out_dscr_err().clear_bit_by_one();
|
w.out_dscr_err().clear_bit_by_one();
|
||||||
w.out_done().clear_bit_by_one();
|
w.out_done().clear_bit_by_one();
|
||||||
@ -123,122 +132,27 @@ impl<const N: u8> RegisterAccess for Channel<N> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset_out() {
|
#[cfg(esp32s3)]
|
||||||
let conf0 = Self::ch().out_conf0();
|
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
|
||||||
conf0.modify(|_, w| w.out_rst().set_bit());
|
|
||||||
conf0.modify(|_, w| w.out_rst().clear_bit());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_out_descriptors(address: u32) {
|
|
||||||
Self::ch()
|
Self::ch()
|
||||||
.out_link()
|
.out_conf1()
|
||||||
.modify(|_, w| unsafe { w.outlink_addr().bits(address) });
|
.modify(|_, w| unsafe { w.out_ext_mem_bk_size().bits(size as u8) });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn set_out_peripheral(peripheral: u8) {
|
impl<const N: u8> TxRegisterAccess for ChannelTxImpl<N> {
|
||||||
Self::ch()
|
fn last_dscr_address(&self) -> usize {
|
||||||
.out_peri_sel()
|
|
||||||
.modify(|_, w| unsafe { w.peri_out_sel().bits(peripheral) });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_out() {
|
|
||||||
Self::ch()
|
|
||||||
.out_link()
|
|
||||||
.modify(|_, w| w.outlink_start().set_bit());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stop_out() {
|
|
||||||
Self::ch()
|
|
||||||
.out_link()
|
|
||||||
.modify(|_, w| w.outlink_stop().set_bit());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn last_out_dscr_address() -> usize {
|
|
||||||
Self::ch()
|
Self::ch()
|
||||||
.out_eof_des_addr()
|
.out_eof_des_addr()
|
||||||
.read()
|
.read()
|
||||||
.out_eof_des_addr()
|
.out_eof_des_addr()
|
||||||
.bits() as _
|
.bits() as _
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(esp32s3)]
|
impl<const N: u8> InterruptAccess<DmaTxInterrupt> for ChannelTxImpl<N> {
|
||||||
fn set_in_ext_mem_block_size(size: DmaExtMemBKSize) {
|
fn listen(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
|
||||||
Self::ch()
|
Self::int().ena().modify(|_, w| {
|
||||||
.in_conf1()
|
|
||||||
.modify(|_, w| unsafe { w.in_ext_mem_bk_size().bits(size as u8) });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_in_burstmode(burst_mode: bool) {
|
|
||||||
Self::ch().in_conf0().modify(|_, w| {
|
|
||||||
w.in_data_burst_en().bit(burst_mode);
|
|
||||||
w.indscr_burst_en().bit(burst_mode)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_in_priority(priority: DmaPriority) {
|
|
||||||
Self::ch()
|
|
||||||
.in_pri()
|
|
||||||
.write(|w| unsafe { w.rx_pri().bits(priority as u8) });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear_in_interrupts() {
|
|
||||||
#[cfg(not(esp32s3))]
|
|
||||||
Self::in_int().clr().write(|w| {
|
|
||||||
w.in_suc_eof().clear_bit_by_one();
|
|
||||||
w.in_err_eof().clear_bit_by_one();
|
|
||||||
w.in_dscr_err().clear_bit_by_one();
|
|
||||||
w.in_dscr_empty().clear_bit_by_one();
|
|
||||||
w.in_done().clear_bit_by_one();
|
|
||||||
w.infifo_ovf().clear_bit_by_one();
|
|
||||||
w.infifo_udf().clear_bit_by_one()
|
|
||||||
});
|
|
||||||
|
|
||||||
#[cfg(esp32s3)]
|
|
||||||
Self::in_int().clr().write(|w| {
|
|
||||||
w.in_suc_eof().clear_bit_by_one();
|
|
||||||
w.in_err_eof().clear_bit_by_one();
|
|
||||||
w.in_dscr_err().clear_bit_by_one();
|
|
||||||
w.in_dscr_empty().clear_bit_by_one();
|
|
||||||
w.in_done().clear_bit_by_one();
|
|
||||||
w.infifo_ovf_l1().clear_bit_by_one();
|
|
||||||
w.infifo_ovf_l3().clear_bit_by_one();
|
|
||||||
w.infifo_udf_l1().clear_bit_by_one();
|
|
||||||
w.infifo_udf_l3().clear_bit_by_one()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset_in() {
|
|
||||||
let conf0 = Self::ch().in_conf0();
|
|
||||||
conf0.modify(|_, w| w.in_rst().set_bit());
|
|
||||||
conf0.modify(|_, w| w.in_rst().clear_bit());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_in_descriptors(address: u32) {
|
|
||||||
Self::ch()
|
|
||||||
.in_link()
|
|
||||||
.modify(|_, w| unsafe { w.inlink_addr().bits(address) });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_in_peripheral(peripheral: u8) {
|
|
||||||
Self::ch()
|
|
||||||
.in_peri_sel()
|
|
||||||
.modify(|_, w| unsafe { w.peri_in_sel().bits(peripheral) });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_in() {
|
|
||||||
Self::ch()
|
|
||||||
.in_link()
|
|
||||||
.modify(|_, w| w.inlink_start().set_bit());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stop_in() {
|
|
||||||
Self::ch()
|
|
||||||
.in_link()
|
|
||||||
.modify(|_, w| w.inlink_stop().set_bit());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn listen_out(interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
|
|
||||||
Self::out_int().ena().modify(|_, w| {
|
|
||||||
for interrupt in interrupts.into() {
|
for interrupt in interrupts.into() {
|
||||||
match interrupt {
|
match interrupt {
|
||||||
DmaTxInterrupt::TotalEof => w.out_total_eof().set_bit(),
|
DmaTxInterrupt::TotalEof => w.out_total_eof().set_bit(),
|
||||||
@ -251,8 +165,8 @@ impl<const N: u8> RegisterAccess for Channel<N> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unlisten_out(interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
|
fn unlisten(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
|
||||||
Self::out_int().ena().modify(|_, w| {
|
Self::int().ena().modify(|_, w| {
|
||||||
for interrupt in interrupts.into() {
|
for interrupt in interrupts.into() {
|
||||||
match interrupt {
|
match interrupt {
|
||||||
DmaTxInterrupt::TotalEof => w.out_total_eof().clear_bit(),
|
DmaTxInterrupt::TotalEof => w.out_total_eof().clear_bit(),
|
||||||
@ -265,10 +179,10 @@ impl<const N: u8> RegisterAccess for Channel<N> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_listening_out() -> EnumSet<DmaTxInterrupt> {
|
fn is_listening(&self) -> EnumSet<DmaTxInterrupt> {
|
||||||
let mut result = EnumSet::new();
|
let mut result = EnumSet::new();
|
||||||
|
|
||||||
let int_ena = Self::out_int().ena().read();
|
let int_ena = Self::int().ena().read();
|
||||||
if int_ena.out_total_eof().bit_is_set() {
|
if int_ena.out_total_eof().bit_is_set() {
|
||||||
result |= DmaTxInterrupt::TotalEof;
|
result |= DmaTxInterrupt::TotalEof;
|
||||||
}
|
}
|
||||||
@ -285,10 +199,24 @@ impl<const N: u8> RegisterAccess for Channel<N> {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pending_out_interrupts() -> EnumSet<DmaTxInterrupt> {
|
fn clear(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
|
||||||
|
Self::int().clr().write(|w| {
|
||||||
|
for interrupt in interrupts.into() {
|
||||||
|
match interrupt {
|
||||||
|
DmaTxInterrupt::TotalEof => w.out_total_eof().clear_bit_by_one(),
|
||||||
|
DmaTxInterrupt::DescriptorError => w.out_dscr_err().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 mut result = EnumSet::new();
|
||||||
|
|
||||||
let int_raw = Self::out_int().raw().read();
|
let int_raw = Self::int().raw().read();
|
||||||
if int_raw.out_total_eof().bit_is_set() {
|
if int_raw.out_total_eof().bit_is_set() {
|
||||||
result |= DmaTxInterrupt::TotalEof;
|
result |= DmaTxInterrupt::TotalEof;
|
||||||
}
|
}
|
||||||
@ -305,22 +233,141 @@ impl<const N: u8> RegisterAccess for Channel<N> {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_out(interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
|
fn waker(&self) -> &'static AtomicWaker {
|
||||||
Self::out_int().clr().write(|w| {
|
&TX_WAKERS[N as usize]
|
||||||
for interrupt in interrupts.into() {
|
}
|
||||||
match interrupt {
|
}
|
||||||
DmaTxInterrupt::TotalEof => w.out_total_eof().clear_bit_by_one(),
|
|
||||||
DmaTxInterrupt::DescriptorError => w.out_dscr_err().clear_bit_by_one(),
|
#[non_exhaustive]
|
||||||
DmaTxInterrupt::Eof => w.out_eof().clear_bit_by_one(),
|
#[doc(hidden)]
|
||||||
DmaTxInterrupt::Done => w.out_done().clear_bit_by_one(),
|
pub struct ChannelRxImpl<const N: u8> {}
|
||||||
};
|
|
||||||
}
|
impl<const N: u8> crate::private::Sealed for ChannelRxImpl<N> {}
|
||||||
w
|
|
||||||
})
|
impl<const N: u8> ChannelRxImpl<N> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn ch() -> &'static crate::peripherals::dma::ch::CH {
|
||||||
|
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
||||||
|
dma.ch(N as usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn listen_in(interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
|
#[cfg(any(esp32c2, esp32c3))]
|
||||||
Self::in_int().ena().modify(|_, w| {
|
#[inline(always)]
|
||||||
|
fn int() -> &'static crate::peripherals::dma::int_ch::INT_CH {
|
||||||
|
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
||||||
|
dma.int_ch(N as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
#[cfg(any(esp32c6, esp32h2))]
|
||||||
|
fn int() -> &'static crate::peripherals::dma::in_int_ch::IN_INT_CH {
|
||||||
|
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
||||||
|
dma.in_int_ch(N as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(esp32s3)]
|
||||||
|
#[inline(always)]
|
||||||
|
fn int() -> &'static crate::peripherals::dma::ch::in_int::IN_INT {
|
||||||
|
let dma = unsafe { &*crate::peripherals::DMA::PTR };
|
||||||
|
dma.ch(N as usize).in_int()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: u8> RegisterAccess for ChannelRxImpl<N> {
|
||||||
|
fn reset(&self) {
|
||||||
|
let conf0 = Self::ch().in_conf0();
|
||||||
|
conf0.modify(|_, w| w.in_rst().set_bit());
|
||||||
|
conf0.modify(|_, w| w.in_rst().clear_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_burst_mode(&self, burst_mode: bool) {
|
||||||
|
Self::ch().in_conf0().modify(|_, w| {
|
||||||
|
w.in_data_burst_en().bit(burst_mode);
|
||||||
|
w.indscr_burst_en().bit(burst_mode)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_priority(&self, priority: DmaPriority) {
|
||||||
|
Self::ch()
|
||||||
|
.in_pri()
|
||||||
|
.write(|w| unsafe { w.rx_pri().bits(priority as u8) });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_peripheral(&self, peripheral: u8) {
|
||||||
|
Self::ch()
|
||||||
|
.in_peri_sel()
|
||||||
|
.modify(|_, w| unsafe { w.peri_in_sel().bits(peripheral) });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_link_addr(&self, address: u32) {
|
||||||
|
Self::ch()
|
||||||
|
.in_link()
|
||||||
|
.modify(|_, w| unsafe { w.inlink_addr().bits(address) });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(&self) {
|
||||||
|
Self::ch()
|
||||||
|
.in_link()
|
||||||
|
.modify(|_, w| w.inlink_start().set_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&self) {
|
||||||
|
Self::ch()
|
||||||
|
.in_link()
|
||||||
|
.modify(|_, w| w.inlink_stop().set_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restart(&self) {
|
||||||
|
Self::ch()
|
||||||
|
.in_link()
|
||||||
|
.modify(|_, w| w.inlink_restart().set_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_interrupts(&self) {
|
||||||
|
#[cfg(not(esp32s3))]
|
||||||
|
Self::int().clr().write(|w| {
|
||||||
|
w.in_suc_eof().clear_bit_by_one();
|
||||||
|
w.in_err_eof().clear_bit_by_one();
|
||||||
|
w.in_dscr_err().clear_bit_by_one();
|
||||||
|
w.in_dscr_empty().clear_bit_by_one();
|
||||||
|
w.in_done().clear_bit_by_one();
|
||||||
|
w.infifo_ovf().clear_bit_by_one();
|
||||||
|
w.infifo_udf().clear_bit_by_one()
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(esp32s3)]
|
||||||
|
Self::int().clr().write(|w| {
|
||||||
|
w.in_suc_eof().clear_bit_by_one();
|
||||||
|
w.in_err_eof().clear_bit_by_one();
|
||||||
|
w.in_dscr_err().clear_bit_by_one();
|
||||||
|
w.in_dscr_empty().clear_bit_by_one();
|
||||||
|
w.in_done().clear_bit_by_one();
|
||||||
|
w.infifo_ovf_l1().clear_bit_by_one();
|
||||||
|
w.infifo_ovf_l3().clear_bit_by_one();
|
||||||
|
w.infifo_udf_l1().clear_bit_by_one();
|
||||||
|
w.infifo_udf_l3().clear_bit_by_one()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(esp32s3)]
|
||||||
|
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
|
||||||
|
Self::ch()
|
||||||
|
.in_conf1()
|
||||||
|
.modify(|_, w| unsafe { w.in_ext_mem_bk_size().bits(size as u8) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: u8> RxRegisterAccess for ChannelRxImpl<N> {
|
||||||
|
fn set_mem2mem_mode(&self, value: bool) {
|
||||||
|
Self::ch()
|
||||||
|
.in_conf0()
|
||||||
|
.modify(|_, w| w.mem_trans_en().bit(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: u8> InterruptAccess<DmaRxInterrupt> for ChannelRxImpl<N> {
|
||||||
|
fn listen(&self, interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
|
||||||
|
Self::int().ena().modify(|_, w| {
|
||||||
for interrupt in interrupts.into() {
|
for interrupt in interrupts.into() {
|
||||||
match interrupt {
|
match interrupt {
|
||||||
DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().set_bit(),
|
DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().set_bit(),
|
||||||
@ -331,11 +378,11 @@ impl<const N: u8> RegisterAccess for Channel<N> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
w
|
w
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unlisten_in(interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
|
fn unlisten(&self, interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
|
||||||
Self::in_int().ena().modify(|_, w| {
|
Self::int().ena().modify(|_, w| {
|
||||||
for interrupt in interrupts.into() {
|
for interrupt in interrupts.into() {
|
||||||
match interrupt {
|
match interrupt {
|
||||||
DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().clear_bit(),
|
DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().clear_bit(),
|
||||||
@ -349,10 +396,10 @@ impl<const N: u8> RegisterAccess for Channel<N> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_listening_in() -> EnumSet<DmaRxInterrupt> {
|
fn is_listening(&self) -> EnumSet<DmaRxInterrupt> {
|
||||||
let mut result = EnumSet::new();
|
let mut result = EnumSet::new();
|
||||||
|
|
||||||
let int_ena = Self::in_int().ena().read();
|
let int_ena = Self::int().ena().read();
|
||||||
if int_ena.in_dscr_err().bit_is_set() {
|
if int_ena.in_dscr_err().bit_is_set() {
|
||||||
result |= DmaRxInterrupt::DescriptorError;
|
result |= DmaRxInterrupt::DescriptorError;
|
||||||
}
|
}
|
||||||
@ -372,10 +419,25 @@ impl<const N: u8> RegisterAccess for Channel<N> {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pending_in_interrupts() -> EnumSet<DmaRxInterrupt> {
|
fn clear(&self, interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
|
||||||
|
Self::int().clr().write(|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.in_dscr_err().clear_bit_by_one(),
|
||||||
|
DmaRxInterrupt::DescriptorEmpty => w.in_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 mut result = EnumSet::new();
|
||||||
|
|
||||||
let int_raw = Self::in_int().raw().read();
|
let int_raw = Self::int().raw().read();
|
||||||
if int_raw.in_dscr_err().bit_is_set() {
|
if int_raw.in_dscr_err().bit_is_set() {
|
||||||
result |= DmaRxInterrupt::DescriptorError;
|
result |= DmaRxInterrupt::DescriptorError;
|
||||||
}
|
}
|
||||||
@ -395,47 +457,7 @@ impl<const N: u8> RegisterAccess for Channel<N> {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_in(interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
|
fn waker(&self) -> &'static AtomicWaker {
|
||||||
Self::in_int().clr().write(|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.in_dscr_err().clear_bit_by_one(),
|
|
||||||
DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().clear_bit_by_one(),
|
|
||||||
DmaRxInterrupt::Done => w.in_done().clear_bit_by_one(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
w
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[non_exhaustive]
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub struct ChannelTxImpl<const N: u8> {}
|
|
||||||
|
|
||||||
use embassy_sync::waitqueue::AtomicWaker;
|
|
||||||
|
|
||||||
static TX_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT];
|
|
||||||
static RX_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT];
|
|
||||||
|
|
||||||
impl<const N: u8> crate::private::Sealed for ChannelTxImpl<N> {}
|
|
||||||
|
|
||||||
impl<const N: u8> TxChannel<Channel<N>> for ChannelTxImpl<N> {
|
|
||||||
fn waker() -> &'static AtomicWaker {
|
|
||||||
&TX_WAKERS[N as usize]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[non_exhaustive]
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub struct ChannelRxImpl<const N: u8> {}
|
|
||||||
|
|
||||||
impl<const N: u8> crate::private::Sealed for ChannelRxImpl<N> {}
|
|
||||||
|
|
||||||
impl<const N: u8> RxChannel<Channel<N>> for ChannelRxImpl<N> {
|
|
||||||
fn waker() -> &'static AtomicWaker {
|
|
||||||
&RX_WAKERS[N as usize]
|
&RX_WAKERS[N as usize]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -477,10 +499,19 @@ macro_rules! impl_channel {
|
|||||||
impl crate::private::Sealed for [<DmaChannel $num>] {}
|
impl crate::private::Sealed for [<DmaChannel $num>] {}
|
||||||
|
|
||||||
impl DmaChannel for [<DmaChannel $num>] {
|
impl DmaChannel for [<DmaChannel $num>] {
|
||||||
type Channel = Channel<$num>;
|
|
||||||
type Rx = ChannelRxImpl<$num>;
|
type Rx = ChannelRxImpl<$num>;
|
||||||
type Tx = ChannelTxImpl<$num>;
|
type Tx = ChannelTxImpl<$num>;
|
||||||
type P = SuitablePeripheral<$num>;
|
type P = SuitablePeripheral<$num>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DmaChannelExt for [<DmaChannel $num>] {
|
||||||
|
fn get_rx_interrupts() -> impl InterruptAccess<DmaRxInterrupt> {
|
||||||
|
ChannelRxImpl::<$num> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_tx_interrupts() -> impl InterruptAccess<DmaTxInterrupt> {
|
||||||
|
ChannelTxImpl::<$num> {}
|
||||||
|
}
|
||||||
|
|
||||||
fn set_isr(handler: $crate::interrupt::InterruptHandler) {
|
fn set_isr(handler: $crate::interrupt::InterruptHandler) {
|
||||||
let mut dma = unsafe { crate::peripherals::DMA::steal() };
|
let mut dma = unsafe { crate::peripherals::DMA::steal() };
|
||||||
@ -497,11 +528,17 @@ macro_rules! impl_channel {
|
|||||||
burst_mode: bool,
|
burst_mode: bool,
|
||||||
priority: DmaPriority,
|
priority: DmaPriority,
|
||||||
) -> crate::dma::Channel<'a, [<DmaChannel $num>], M> {
|
) -> crate::dma::Channel<'a, [<DmaChannel $num>], M> {
|
||||||
let mut tx_impl = ChannelTxImpl {};
|
let tx_impl = ChannelTxImpl {};
|
||||||
tx_impl.init(burst_mode, priority);
|
tx_impl.set_burst_mode(burst_mode);
|
||||||
|
tx_impl.set_priority(priority);
|
||||||
|
|
||||||
let mut rx_impl = ChannelRxImpl {};
|
let rx_impl = ChannelRxImpl {};
|
||||||
rx_impl.init(burst_mode, priority);
|
rx_impl.set_burst_mode(burst_mode);
|
||||||
|
rx_impl.set_priority(priority);
|
||||||
|
// clear the mem2mem mode to avoid failed DMA if this
|
||||||
|
// channel was previously used for a mem2mem transfer.
|
||||||
|
#[cfg(gdma)]
|
||||||
|
rx_impl.set_mem2mem_mode(false);
|
||||||
|
|
||||||
crate::dma::Channel {
|
crate::dma::Channel {
|
||||||
tx: ChannelTx::new(tx_impl, burst_mode),
|
tx: ChannelTx::new(tx_impl, burst_mode),
|
||||||
|
|||||||
@ -1497,17 +1497,20 @@ impl RxCircularState {
|
|||||||
|
|
||||||
/// A description of a DMA Channel.
|
/// A description of a DMA Channel.
|
||||||
pub trait DmaChannel: crate::private::Sealed {
|
pub trait DmaChannel: crate::private::Sealed {
|
||||||
#[doc(hidden)]
|
|
||||||
type Channel: RegisterAccess;
|
|
||||||
|
|
||||||
/// A description of the RX half of a DMA Channel.
|
/// A description of the RX half of a DMA Channel.
|
||||||
type Rx: RxChannel<Self::Channel>;
|
type Rx: RxRegisterAccess + InterruptAccess<DmaRxInterrupt>;
|
||||||
|
|
||||||
/// A description of the TX half of a DMA Channel.
|
/// A description of the TX half of a DMA Channel.
|
||||||
type Tx: TxChannel<Self::Channel>;
|
type Tx: TxRegisterAccess + InterruptAccess<DmaTxInterrupt>;
|
||||||
|
|
||||||
/// A suitable peripheral for this DMA channel.
|
/// A suitable peripheral for this DMA channel.
|
||||||
type P: PeripheralMarker;
|
type P: PeripheralMarker;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub trait DmaChannelExt: DmaChannel {
|
||||||
|
fn get_rx_interrupts() -> impl InterruptAccess<DmaRxInterrupt>;
|
||||||
|
fn get_tx_interrupts() -> impl InterruptAccess<DmaTxInterrupt>;
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
fn set_isr(handler: InterruptHandler);
|
fn set_isr(handler: InterruptHandler);
|
||||||
@ -1516,8 +1519,6 @@ pub trait DmaChannel: crate::private::Sealed {
|
|||||||
/// The functions here are not meant to be used outside the HAL
|
/// The functions here are not meant to be used outside the HAL
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub trait Rx: crate::private::Sealed {
|
pub trait Rx: crate::private::Sealed {
|
||||||
fn init(&mut self, burst_mode: bool, priority: DmaPriority);
|
|
||||||
|
|
||||||
unsafe fn prepare_transfer_without_start(
|
unsafe fn prepare_transfer_without_start(
|
||||||
&mut self,
|
&mut self,
|
||||||
peri: DmaPeripheral,
|
peri: DmaPeripheral,
|
||||||
@ -1569,53 +1570,7 @@ pub trait Rx: crate::private::Sealed {
|
|||||||
|
|
||||||
fn clear_interrupts(&self);
|
fn clear_interrupts(&self);
|
||||||
|
|
||||||
fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker;
|
fn waker(&self) -> &'static embassy_sync::waitqueue::AtomicWaker;
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub trait RxChannel<R>: crate::private::Sealed
|
|
||||||
where
|
|
||||||
R: RegisterAccess,
|
|
||||||
{
|
|
||||||
fn init(&mut self, burst_mode: bool, priority: DmaPriority) {
|
|
||||||
R::set_in_burstmode(burst_mode);
|
|
||||||
R::set_in_priority(priority);
|
|
||||||
// clear the mem2mem mode to avoid failed DMA if this
|
|
||||||
// channel was previously used for a mem2mem transfer.
|
|
||||||
#[cfg(gdma)]
|
|
||||||
R::set_mem2mem_mode(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn prepare_transfer_without_start(
|
|
||||||
&mut self,
|
|
||||||
first_desc: *mut DmaDescriptor,
|
|
||||||
peri: DmaPeripheral,
|
|
||||||
) -> Result<(), DmaError> {
|
|
||||||
compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
|
||||||
|
|
||||||
R::clear_in_interrupts();
|
|
||||||
R::reset_in();
|
|
||||||
R::set_in_descriptors(first_desc as u32);
|
|
||||||
R::set_in_peripheral(peri as u8);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_transfer(&mut self) -> Result<(), DmaError> {
|
|
||||||
R::start_in();
|
|
||||||
|
|
||||||
if R::pending_in_interrupts().contains(DmaRxInterrupt::DescriptorError) {
|
|
||||||
Err(DmaError::DescriptorError)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stop_transfer(&mut self) {
|
|
||||||
R::stop_in();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DMA receive channel
|
// DMA receive channel
|
||||||
@ -1649,10 +1604,6 @@ impl<'a, CH> Rx for ChannelRx<'a, CH>
|
|||||||
where
|
where
|
||||||
CH: DmaChannel,
|
CH: DmaChannel,
|
||||||
{
|
{
|
||||||
fn init(&mut self, burst_mode: bool, priority: DmaPriority) {
|
|
||||||
self.rx_impl.init(burst_mode, priority);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn prepare_transfer_without_start(
|
unsafe fn prepare_transfer_without_start(
|
||||||
&mut self,
|
&mut self,
|
||||||
peri: DmaPeripheral,
|
peri: DmaPeripheral,
|
||||||
@ -1685,8 +1636,14 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.rx_impl
|
compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
||||||
.prepare_transfer_without_start(chain.first() as _, peri)
|
|
||||||
|
self.rx_impl.clear_interrupts();
|
||||||
|
self.rx_impl.reset();
|
||||||
|
self.rx_impl.set_link_addr(chain.first() as u32);
|
||||||
|
self.rx_impl.set_peripheral(peri as u8);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn prepare_transfer<BUF: DmaRxBuffer>(
|
unsafe fn prepare_transfer<BUF: DmaRxBuffer>(
|
||||||
@ -1701,46 +1658,61 @@ where
|
|||||||
return Err(DmaError::InvalidAlignment);
|
return Err(DmaError::InvalidAlignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.rx_impl
|
compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
||||||
.prepare_transfer_without_start(preparation.start, peri)
|
|
||||||
|
self.rx_impl.clear_interrupts();
|
||||||
|
self.rx_impl.reset();
|
||||||
|
self.rx_impl.set_link_addr(preparation.start as u32);
|
||||||
|
self.rx_impl.set_peripheral(peri as u8);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_transfer(&mut self) -> Result<(), DmaError> {
|
fn start_transfer(&mut self) -> Result<(), DmaError> {
|
||||||
self.rx_impl.start_transfer()
|
self.rx_impl.start();
|
||||||
|
|
||||||
|
if self
|
||||||
|
.pending_in_interrupts()
|
||||||
|
.contains(DmaRxInterrupt::DescriptorError)
|
||||||
|
{
|
||||||
|
Err(DmaError::DescriptorError)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop_transfer(&mut self) {
|
fn stop_transfer(&mut self) {
|
||||||
self.rx_impl.stop_transfer()
|
self.rx_impl.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(esp32s3)]
|
#[cfg(esp32s3)]
|
||||||
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
|
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
|
||||||
CH::Channel::set_in_ext_mem_block_size(size);
|
self.rx_impl.set_ext_mem_block_size(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(gdma)]
|
#[cfg(gdma)]
|
||||||
fn set_mem2mem_mode(&mut self, value: bool) {
|
fn set_mem2mem_mode(&mut self, value: bool) {
|
||||||
CH::Channel::set_mem2mem_mode(value);
|
self.rx_impl.set_mem2mem_mode(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn listen_in(&self, interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
|
fn listen_in(&self, interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
|
||||||
CH::Channel::listen_in(interrupts);
|
self.rx_impl.listen(interrupts);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unlisten_in(&self, interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
|
fn unlisten_in(&self, interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
|
||||||
CH::Channel::unlisten_in(interrupts);
|
self.rx_impl.unlisten(interrupts);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_listening_in(&self) -> EnumSet<DmaRxInterrupt> {
|
fn is_listening_in(&self) -> EnumSet<DmaRxInterrupt> {
|
||||||
CH::Channel::is_listening_in()
|
self.rx_impl.is_listening()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_in(&self, interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
|
fn clear_in(&self, interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
|
||||||
CH::Channel::clear_in(interrupts);
|
self.rx_impl.clear(interrupts);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pending_in_interrupts(&self) -> EnumSet<DmaRxInterrupt> {
|
fn pending_in_interrupts(&self) -> EnumSet<DmaRxInterrupt> {
|
||||||
CH::Channel::pending_in_interrupts()
|
self.rx_impl.pending_interrupts()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_done(&self) -> bool {
|
fn is_done(&self) -> bool {
|
||||||
@ -1749,19 +1721,17 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn clear_interrupts(&self) {
|
fn clear_interrupts(&self) {
|
||||||
CH::Channel::clear_in_interrupts();
|
self.rx_impl.clear_interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker {
|
fn waker(&self) -> &'static embassy_sync::waitqueue::AtomicWaker {
|
||||||
CH::Rx::waker()
|
self.rx_impl.waker()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The functions here are not meant to be used outside the HAL
|
/// The functions here are not meant to be used outside the HAL
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub trait Tx: crate::private::Sealed {
|
pub trait Tx: crate::private::Sealed {
|
||||||
fn init(&mut self, burst_mode: bool, priority: DmaPriority);
|
|
||||||
|
|
||||||
unsafe fn prepare_transfer_without_start(
|
unsafe fn prepare_transfer_without_start(
|
||||||
&mut self,
|
&mut self,
|
||||||
peri: DmaPeripheral,
|
peri: DmaPeripheral,
|
||||||
@ -1803,69 +1773,11 @@ pub trait Tx: crate::private::Sealed {
|
|||||||
|
|
||||||
fn clear_interrupts(&self);
|
fn clear_interrupts(&self);
|
||||||
|
|
||||||
fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker;
|
fn waker(&self) -> &'static embassy_sync::waitqueue::AtomicWaker;
|
||||||
|
|
||||||
fn last_out_dscr_address(&self) -> usize;
|
fn last_out_dscr_address(&self) -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub trait TxChannel<R>: crate::private::Sealed
|
|
||||||
where
|
|
||||||
R: RegisterAccess,
|
|
||||||
{
|
|
||||||
fn init(&mut self, burst_mode: bool, priority: DmaPriority) {
|
|
||||||
R::set_out_burstmode(burst_mode);
|
|
||||||
R::set_out_priority(priority);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn prepare_transfer_without_start(
|
|
||||||
&mut self,
|
|
||||||
first_desc: *mut DmaDescriptor,
|
|
||||||
peri: DmaPeripheral,
|
|
||||||
) -> Result<(), DmaError> {
|
|
||||||
compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
|
||||||
|
|
||||||
R::clear_out_interrupts();
|
|
||||||
R::reset_out();
|
|
||||||
R::set_out_descriptors(first_desc as u32);
|
|
||||||
R::set_out_peripheral(peri as u8);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_transfer(&mut self) -> Result<(), DmaError> {
|
|
||||||
R::start_out();
|
|
||||||
|
|
||||||
if R::pending_out_interrupts().contains(DmaTxInterrupt::DescriptorError) {
|
|
||||||
Err(DmaError::DescriptorError)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stop_transfer(&mut self) {
|
|
||||||
R::stop_out();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn listen_out(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
|
|
||||||
R::listen_out(interrupts)
|
|
||||||
}
|
|
||||||
fn unlisten_out(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
|
|
||||||
R::unlisten_out(interrupts)
|
|
||||||
}
|
|
||||||
fn is_listening_out(&self) -> EnumSet<DmaTxInterrupt> {
|
|
||||||
R::is_listening_out()
|
|
||||||
}
|
|
||||||
fn clear_out(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
|
|
||||||
R::clear_out(interrupts)
|
|
||||||
}
|
|
||||||
fn pending_out_interrupts(&self) -> EnumSet<DmaTxInterrupt> {
|
|
||||||
R::pending_out_interrupts()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// DMA transmit channel
|
/// DMA transmit channel
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub struct ChannelTx<'a, CH>
|
pub struct ChannelTx<'a, CH>
|
||||||
@ -1897,10 +1809,6 @@ impl<'a, CH> Tx for ChannelTx<'a, CH>
|
|||||||
where
|
where
|
||||||
CH: DmaChannel,
|
CH: DmaChannel,
|
||||||
{
|
{
|
||||||
fn init(&mut self, burst_mode: bool, priority: DmaPriority) {
|
|
||||||
self.tx_impl.init(burst_mode, priority);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn prepare_transfer_without_start(
|
unsafe fn prepare_transfer_without_start(
|
||||||
&mut self,
|
&mut self,
|
||||||
peri: DmaPeripheral,
|
peri: DmaPeripheral,
|
||||||
@ -1922,8 +1830,15 @@ where
|
|||||||
crate::soc::cache_writeback_addr(des.buffer as u32, des.size() as u32);
|
crate::soc::cache_writeback_addr(des.buffer as u32, des.size() as u32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.tx_impl
|
|
||||||
.prepare_transfer_without_start(chain.first() as _, peri)
|
compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
||||||
|
|
||||||
|
self.tx_impl.clear_interrupts();
|
||||||
|
self.tx_impl.reset();
|
||||||
|
self.tx_impl.set_link_addr(chain.first() as u32);
|
||||||
|
self.tx_impl.set_peripheral(peri as u8);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn prepare_transfer<BUF: DmaTxBuffer>(
|
unsafe fn prepare_transfer<BUF: DmaTxBuffer>(
|
||||||
@ -1945,95 +1860,127 @@ where
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
// TODO: Get burst mode from DmaBuf.
|
// TODO: Get burst mode from DmaBuf.
|
||||||
self.tx_impl
|
|
||||||
.prepare_transfer_without_start(preparation.start, peri)
|
compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
||||||
|
|
||||||
|
self.tx_impl.clear_interrupts();
|
||||||
|
self.tx_impl.reset();
|
||||||
|
self.tx_impl.set_link_addr(preparation.start as u32);
|
||||||
|
self.tx_impl.set_peripheral(peri as u8);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_transfer(&mut self) -> Result<(), DmaError> {
|
fn start_transfer(&mut self) -> Result<(), DmaError> {
|
||||||
self.tx_impl.start_transfer()
|
self.tx_impl.start();
|
||||||
|
|
||||||
|
if self
|
||||||
|
.pending_out_interrupts()
|
||||||
|
.contains(DmaTxInterrupt::DescriptorError)
|
||||||
|
{
|
||||||
|
Err(DmaError::DescriptorError)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop_transfer(&mut self) {
|
fn stop_transfer(&mut self) {
|
||||||
self.tx_impl.stop_transfer()
|
self.tx_impl.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(esp32s3)]
|
#[cfg(esp32s3)]
|
||||||
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
|
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
|
||||||
CH::Channel::set_out_ext_mem_block_size(size);
|
self.tx_impl.set_ext_mem_block_size(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn listen_out(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
|
fn listen_out(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
|
||||||
CH::Channel::listen_out(interrupts);
|
self.tx_impl.listen(interrupts);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unlisten_out(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
|
fn unlisten_out(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
|
||||||
CH::Channel::unlisten_out(interrupts);
|
self.tx_impl.unlisten(interrupts);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_listening_out(&self) -> EnumSet<DmaTxInterrupt> {
|
fn is_listening_out(&self) -> EnumSet<DmaTxInterrupt> {
|
||||||
CH::Channel::is_listening_out()
|
self.tx_impl.is_listening()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_out(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
|
fn clear_out(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
|
||||||
CH::Channel::clear_out(interrupts);
|
self.tx_impl.clear(interrupts);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pending_out_interrupts(&self) -> EnumSet<DmaTxInterrupt> {
|
fn pending_out_interrupts(&self) -> EnumSet<DmaTxInterrupt> {
|
||||||
CH::Channel::pending_out_interrupts()
|
self.tx_impl.pending_interrupts()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn waker() -> &'static embassy_sync::waitqueue::AtomicWaker {
|
fn waker(&self) -> &'static embassy_sync::waitqueue::AtomicWaker {
|
||||||
CH::Tx::waker()
|
self.tx_impl.waker()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_interrupts(&self) {
|
fn clear_interrupts(&self) {
|
||||||
CH::Channel::clear_out_interrupts();
|
self.tx_impl.clear_interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn last_out_dscr_address(&self) -> usize {
|
fn last_out_dscr_address(&self) -> usize {
|
||||||
CH::Channel::last_out_dscr_address()
|
self.tx_impl.last_dscr_address()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub trait RegisterAccess: crate::private::Sealed {
|
pub trait RegisterAccess: crate::private::Sealed {
|
||||||
|
/// Reset the state machine of the channel and FIFO pointer.
|
||||||
|
fn reset(&self);
|
||||||
|
|
||||||
|
/// Enable/Disable INCR burst transfer for channel reading
|
||||||
|
/// descriptor and accessing data in internal RAM.
|
||||||
|
fn set_burst_mode(&self, burst_mode: bool);
|
||||||
|
|
||||||
|
/// The priority of the channel. The larger the value, the higher the
|
||||||
|
/// priority.
|
||||||
|
fn set_priority(&self, priority: DmaPriority);
|
||||||
|
|
||||||
|
/// Select a peripheral for the channel.
|
||||||
|
fn set_peripheral(&self, peripheral: u8);
|
||||||
|
|
||||||
|
/// Set the address of the first descriptor.
|
||||||
|
fn set_link_addr(&self, address: u32);
|
||||||
|
|
||||||
|
/// Enable the channel for data transfer.
|
||||||
|
fn start(&self);
|
||||||
|
|
||||||
|
/// Stop the channel from transferring data.
|
||||||
|
fn stop(&self);
|
||||||
|
|
||||||
|
/// Mount a new descriptor.
|
||||||
|
fn restart(&self);
|
||||||
|
|
||||||
|
/// Clear all interrupt bits
|
||||||
|
fn clear_interrupts(&self);
|
||||||
|
|
||||||
|
#[cfg(esp32s3)]
|
||||||
|
fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub trait RxRegisterAccess: RegisterAccess {
|
||||||
#[cfg(gdma)]
|
#[cfg(gdma)]
|
||||||
fn set_mem2mem_mode(value: bool);
|
fn set_mem2mem_mode(&self, value: bool);
|
||||||
#[cfg(esp32s3)]
|
}
|
||||||
fn set_out_ext_mem_block_size(size: DmaExtMemBKSize);
|
|
||||||
fn set_out_burstmode(burst_mode: bool);
|
|
||||||
fn set_out_priority(priority: DmaPriority);
|
|
||||||
fn clear_out_interrupts();
|
|
||||||
fn reset_out();
|
|
||||||
fn set_out_descriptors(address: u32);
|
|
||||||
fn set_out_peripheral(peripheral: u8);
|
|
||||||
fn start_out();
|
|
||||||
fn stop_out();
|
|
||||||
|
|
||||||
fn listen_out(interrupts: impl Into<EnumSet<DmaTxInterrupt>>);
|
#[doc(hidden)]
|
||||||
fn unlisten_out(interrupts: impl Into<EnumSet<DmaTxInterrupt>>);
|
pub trait TxRegisterAccess: RegisterAccess {
|
||||||
fn is_listening_out() -> EnumSet<DmaTxInterrupt>;
|
/// Outlink descriptor address when EOF occurs of Tx channel.
|
||||||
fn clear_out(interrupts: impl Into<EnumSet<DmaTxInterrupt>>);
|
fn last_dscr_address(&self) -> usize;
|
||||||
fn pending_out_interrupts() -> EnumSet<DmaTxInterrupt>;
|
}
|
||||||
|
|
||||||
fn listen_in(interrupts: impl Into<EnumSet<DmaRxInterrupt>>);
|
#[doc(hidden)]
|
||||||
fn unlisten_in(interrupts: impl Into<EnumSet<DmaRxInterrupt>>);
|
pub trait InterruptAccess<T: EnumSetType>: crate::private::Sealed {
|
||||||
fn is_listening_in() -> EnumSet<DmaRxInterrupt>;
|
fn listen(&self, interrupts: impl Into<EnumSet<T>>);
|
||||||
fn clear_in(interrupts: impl Into<EnumSet<DmaRxInterrupt>>);
|
fn unlisten(&self, interrupts: impl Into<EnumSet<T>>);
|
||||||
fn pending_in_interrupts() -> EnumSet<DmaRxInterrupt>;
|
fn is_listening(&self) -> EnumSet<T>;
|
||||||
|
fn clear(&self, interrupts: impl Into<EnumSet<T>>);
|
||||||
fn last_out_dscr_address() -> usize;
|
fn pending_interrupts(&self) -> EnumSet<T>;
|
||||||
|
fn waker(&self) -> &'static embassy_sync::waitqueue::AtomicWaker;
|
||||||
#[cfg(esp32s3)]
|
|
||||||
fn set_in_ext_mem_block_size(size: DmaExtMemBKSize);
|
|
||||||
fn set_in_burstmode(burst_mode: bool);
|
|
||||||
fn set_in_priority(priority: DmaPriority);
|
|
||||||
fn clear_in_interrupts();
|
|
||||||
fn reset_in();
|
|
||||||
fn set_in_descriptors(address: u32);
|
|
||||||
fn set_in_peripheral(peripheral: u8);
|
|
||||||
fn start_in();
|
|
||||||
fn stop_in();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// DMA Channel
|
/// DMA Channel
|
||||||
@ -2057,7 +2004,10 @@ where
|
|||||||
/// with [crate::interrupt::Priority::max()]
|
/// with [crate::interrupt::Priority::max()]
|
||||||
///
|
///
|
||||||
/// Interrupts are not enabled at the peripheral level here.
|
/// Interrupts are not enabled at the peripheral level here.
|
||||||
pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
|
pub fn set_interrupt_handler(&mut self, handler: InterruptHandler)
|
||||||
|
where
|
||||||
|
C: DmaChannelExt,
|
||||||
|
{
|
||||||
C::set_isr(handler);
|
C::set_isr(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3001,7 +2951,7 @@ pub(crate) mod asynch {
|
|||||||
self: core::pin::Pin<&mut Self>,
|
self: core::pin::Pin<&mut Self>,
|
||||||
cx: &mut core::task::Context<'_>,
|
cx: &mut core::task::Context<'_>,
|
||||||
) -> Poll<Self::Output> {
|
) -> Poll<Self::Output> {
|
||||||
TX::waker().register(cx.waker());
|
self.tx.waker().register(cx.waker());
|
||||||
if self.tx.is_done() {
|
if self.tx.is_done() {
|
||||||
self.tx.clear_interrupts();
|
self.tx.clear_interrupts();
|
||||||
Poll::Ready(Ok(()))
|
Poll::Ready(Ok(()))
|
||||||
@ -3057,7 +3007,7 @@ pub(crate) mod asynch {
|
|||||||
self: core::pin::Pin<&mut Self>,
|
self: core::pin::Pin<&mut Self>,
|
||||||
cx: &mut core::task::Context<'_>,
|
cx: &mut core::task::Context<'_>,
|
||||||
) -> Poll<Self::Output> {
|
) -> Poll<Self::Output> {
|
||||||
RX::waker().register(cx.waker());
|
self.rx.waker().register(cx.waker());
|
||||||
if self.rx.is_done() {
|
if self.rx.is_done() {
|
||||||
self.rx.clear_interrupts();
|
self.rx.clear_interrupts();
|
||||||
Poll::Ready(Ok(()))
|
Poll::Ready(Ok(()))
|
||||||
@ -3123,7 +3073,7 @@ pub(crate) mod asynch {
|
|||||||
self: core::pin::Pin<&mut Self>,
|
self: core::pin::Pin<&mut Self>,
|
||||||
cx: &mut core::task::Context<'_>,
|
cx: &mut core::task::Context<'_>,
|
||||||
) -> Poll<Self::Output> {
|
) -> Poll<Self::Output> {
|
||||||
TX::waker().register(cx.waker());
|
self.tx.waker().register(cx.waker());
|
||||||
if self
|
if self
|
||||||
.tx
|
.tx
|
||||||
.pending_out_interrupts()
|
.pending_out_interrupts()
|
||||||
@ -3187,7 +3137,7 @@ pub(crate) mod asynch {
|
|||||||
self: core::pin::Pin<&mut Self>,
|
self: core::pin::Pin<&mut Self>,
|
||||||
cx: &mut core::task::Context<'_>,
|
cx: &mut core::task::Context<'_>,
|
||||||
) -> Poll<Self::Output> {
|
) -> Poll<Self::Output> {
|
||||||
RX::waker().register(cx.waker());
|
self.rx.waker().register(cx.waker());
|
||||||
if self
|
if self
|
||||||
.rx
|
.rx
|
||||||
.pending_in_interrupts()
|
.pending_in_interrupts()
|
||||||
@ -3229,49 +3179,58 @@ pub(crate) mod asynch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_interrupt<CH: DmaChannel>() {
|
fn handle_interrupt<CH: DmaChannelExt>() {
|
||||||
if CH::Channel::pending_in_interrupts().is_disjoint(
|
let rx = CH::get_rx_interrupts();
|
||||||
|
let tx = CH::get_tx_interrupts();
|
||||||
|
|
||||||
|
if rx.pending_interrupts().is_disjoint(
|
||||||
DmaRxInterrupt::DescriptorError
|
DmaRxInterrupt::DescriptorError
|
||||||
| DmaRxInterrupt::DescriptorEmpty
|
| DmaRxInterrupt::DescriptorEmpty
|
||||||
| DmaRxInterrupt::ErrorEof,
|
| DmaRxInterrupt::ErrorEof,
|
||||||
) {
|
) {
|
||||||
CH::Channel::unlisten_in(
|
rx.unlisten(
|
||||||
DmaRxInterrupt::DescriptorError
|
DmaRxInterrupt::DescriptorError
|
||||||
| DmaRxInterrupt::DescriptorEmpty
|
| DmaRxInterrupt::DescriptorEmpty
|
||||||
| DmaRxInterrupt::ErrorEof
|
| DmaRxInterrupt::ErrorEof
|
||||||
| DmaRxInterrupt::SuccessfulEof
|
| DmaRxInterrupt::SuccessfulEof
|
||||||
| DmaRxInterrupt::Done,
|
| DmaRxInterrupt::Done,
|
||||||
);
|
);
|
||||||
CH::Rx::waker().wake()
|
rx.waker().wake()
|
||||||
}
|
}
|
||||||
|
|
||||||
if CH::Channel::pending_out_interrupts().contains(DmaTxInterrupt::DescriptorError) {
|
if tx
|
||||||
CH::Channel::unlisten_out(
|
.pending_interrupts()
|
||||||
|
.contains(DmaTxInterrupt::DescriptorError)
|
||||||
|
{
|
||||||
|
tx.unlisten(
|
||||||
DmaTxInterrupt::DescriptorError | DmaTxInterrupt::TotalEof | DmaTxInterrupt::Done,
|
DmaTxInterrupt::DescriptorError | DmaTxInterrupt::TotalEof | DmaTxInterrupt::Done,
|
||||||
);
|
);
|
||||||
CH::Tx::waker().wake()
|
tx.waker().wake()
|
||||||
}
|
}
|
||||||
|
|
||||||
if CH::Channel::pending_in_interrupts().contains(DmaRxInterrupt::SuccessfulEof) {
|
if rx
|
||||||
CH::Channel::unlisten_in(DmaRxInterrupt::SuccessfulEof);
|
.pending_interrupts()
|
||||||
CH::Rx::waker().wake()
|
.contains(DmaRxInterrupt::SuccessfulEof)
|
||||||
}
|
|
||||||
|
|
||||||
if CH::Channel::pending_in_interrupts().contains(DmaRxInterrupt::Done) {
|
|
||||||
CH::Channel::unlisten_in(DmaRxInterrupt::Done);
|
|
||||||
CH::Rx::waker().wake()
|
|
||||||
}
|
|
||||||
|
|
||||||
if CH::Channel::pending_out_interrupts().contains(DmaTxInterrupt::TotalEof)
|
|
||||||
&& CH::Channel::is_listening_out().contains(DmaTxInterrupt::TotalEof)
|
|
||||||
{
|
{
|
||||||
CH::Channel::unlisten_out(DmaTxInterrupt::TotalEof);
|
rx.unlisten(DmaRxInterrupt::SuccessfulEof);
|
||||||
CH::Tx::waker().wake()
|
rx.waker().wake()
|
||||||
}
|
}
|
||||||
|
|
||||||
if CH::Channel::pending_out_interrupts().contains(DmaTxInterrupt::Done) {
|
if rx.pending_interrupts().contains(DmaRxInterrupt::Done) {
|
||||||
CH::Channel::unlisten_out(DmaTxInterrupt::Done);
|
rx.unlisten(DmaRxInterrupt::Done);
|
||||||
CH::Tx::waker().wake()
|
rx.waker().wake()
|
||||||
|
}
|
||||||
|
|
||||||
|
if tx.pending_interrupts().contains(DmaTxInterrupt::TotalEof)
|
||||||
|
&& tx.is_listening().contains(DmaTxInterrupt::TotalEof)
|
||||||
|
{
|
||||||
|
tx.unlisten(DmaTxInterrupt::TotalEof);
|
||||||
|
tx.waker().wake()
|
||||||
|
}
|
||||||
|
|
||||||
|
if tx.pending_interrupts().contains(DmaTxInterrupt::Done) {
|
||||||
|
tx.unlisten(DmaTxInterrupt::Done);
|
||||||
|
tx.waker().wake()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user