#![cfg_attr(docsrs, procmacros::doc_replace(
"dma_channel" => {
cfg(any(esp32, esp32s2)) => "DMA_SPI2",
_ => "DMA_CH0",
},
))]
#![cfg_attr(
spi_slave_supports_dma,
doc = r#"
## Examples
### SPI Slave with DMA
```rust, no_run
# {before_snippet}
# use esp_hal::dma_buffers;
# use esp_hal::dma::{DmaRxBuf, DmaTxBuf};
# use esp_hal::spi::Mode;
# use esp_hal::spi::slave::Spi;
let sclk = peripherals.GPIO0;
let miso = peripherals.GPIO1;
let mosi = peripherals.GPIO2;
let cs = peripherals.GPIO3;
let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(32000);
let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();
let dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap();
let mut spi = Spi::new(peripherals.SPI2, Mode::_0)
.with_sck(sclk)
.with_mosi(mosi)
.with_miso(miso)
.with_cs(cs)
.with_dma(peripherals.__dma_channel__);
let transfer = spi.transfer(50, dma_rx_buf, 50, dma_tx_buf)?;
transfer.wait();
# {after_snippet}
```
"#
)]
#![cfg_attr(esp32, doc = "- ESP32 only supports SPI mode 1 and 3.\n\n")]
use core::marker::PhantomData;
use super::Mode;
use crate::{
Blocking,
DriverMode,
gpio::{
InputSignal,
NoPin,
OutputConfig,
OutputSignal,
interconnect::{PeripheralInput, PeripheralOutput},
},
pac::spi2::RegisterBlock,
system::PeripheralGuard,
};
#[instability::unstable]
pub struct Spi<'d, Dm: DriverMode> {
spi: AnySpi<'d>,
#[allow(dead_code)]
data_mode: Mode,
_mode: PhantomData<Dm>,
_guard: PeripheralGuard,
}
impl<'d> Spi<'d, Blocking> {
#[instability::unstable]
pub fn new(spi: impl Instance + 'd, mode: Mode) -> Spi<'d, Blocking> {
let guard = PeripheralGuard::new(spi.info().peripheral);
let this = Spi {
spi: spi.degrade(),
data_mode: mode,
_mode: PhantomData,
_guard: guard,
};
this.spi.info().init();
this.spi.info().set_data_mode(mode, false);
this.with_mosi(NoPin)
.with_miso(NoPin)
.with_sck(NoPin)
.with_cs(NoPin)
}
fn connect_input_pin(&self, pin: impl PeripheralInput<'d>, signal: InputSignal) {
let pin = pin.into();
pin.set_input_enable(true);
signal.connect_to(&pin);
}
#[instability::unstable]
pub fn with_sck(self, sclk: impl PeripheralInput<'d>) -> Self {
self.connect_input_pin(sclk, self.spi.info().sclk);
self
}
#[instability::unstable]
pub fn with_mosi(self, mosi: impl PeripheralInput<'d>) -> Self {
self.connect_input_pin(mosi, self.spi.info().mosi);
self
}
#[instability::unstable]
pub fn with_miso(self, miso: impl PeripheralOutput<'d>) -> Self {
let miso = miso.into();
miso.apply_output_config(&OutputConfig::default());
miso.set_output_enable(true);
self.spi.info().miso.connect_to(&miso);
self
}
#[instability::unstable]
pub fn with_cs(self, cs: impl PeripheralInput<'d>) -> Self {
self.connect_input_pin(cs, self.spi.info().cs);
self
}
}
#[instability::unstable]
#[cfg(spi_slave_supports_dma)]
pub mod dma {
use core::mem::ManuallyDrop;
use enumset::enum_set;
use super::*;
use crate::{
DriverMode,
dma::{
Channel,
DmaChannelFor,
DmaEligible,
DmaRxBuffer,
DmaRxInterrupt,
DmaTxBuffer,
EmptyBuf,
PeripheralDmaChannel,
},
spi::Error,
};
const MAX_DMA_SIZE: usize = 32768 - 32;
impl<'d> Spi<'d, Blocking> {
#[cfg_attr(esp32, doc = "\n\n**Note**: ESP32 only supports Mode 1 and 3.")]
#[instability::unstable]
pub fn with_dma(self, channel: impl DmaChannelFor<AnySpi<'d>>) -> SpiDma<'d, Blocking> {
self.spi.info().set_data_mode(self.data_mode, true);
SpiDma::new(self.spi, channel.degrade())
}
}
#[instability::unstable]
pub struct SpiDma<'d, Dm>
where
Dm: DriverMode,
{
pub(crate) spi: AnySpi<'d>,
pub(crate) channel: Channel<Dm, PeripheralDmaChannel<AnySpi<'d>>>,
_guard: PeripheralGuard,
}
impl<Dm> core::fmt::Debug for SpiDma<'_, Dm>
where
Dm: DriverMode,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SpiDma").finish()
}
}
impl<'d> SpiDma<'d, Blocking> {
fn new(spi: AnySpi<'d>, channel: PeripheralDmaChannel<AnySpi<'d>>) -> Self {
let channel = Channel::new(channel);
channel.runtime_ensure_compatible(&spi);
let guard = PeripheralGuard::new(spi.info().peripheral);
Self {
spi,
channel,
_guard: guard,
}
}
}
impl<'d, Dm> SpiDma<'d, Dm>
where
Dm: DriverMode,
{
fn driver(&self) -> DmaDriver {
DmaDriver {
info: self.spi.info(),
dma_peripheral: self.spi.dma_peripheral(),
}
}
#[instability::unstable]
pub fn write<TX>(
mut self,
bytes_to_write: usize,
mut buffer: TX,
) -> Result<SpiDmaTransfer<'d, Dm, TX>, (Error, Self, TX)>
where
TX: DmaTxBuffer,
{
if bytes_to_write > MAX_DMA_SIZE {
return Err((Error::MaxDmaTransferSizeExceeded, self, buffer));
}
let result = unsafe {
self.driver().start_transfer_dma(
0,
bytes_to_write,
&mut EmptyBuf,
&mut buffer,
&mut self.channel,
)
};
if let Err(err) = result {
return Err((err, self, buffer));
}
Ok(SpiDmaTransfer::new(self, buffer, false, true))
}
#[instability::unstable]
pub fn read<RX>(
mut self,
bytes_to_read: usize,
mut buffer: RX,
) -> Result<SpiDmaTransfer<'d, Dm, RX>, (Error, Self, RX)>
where
RX: DmaRxBuffer,
{
if bytes_to_read > MAX_DMA_SIZE {
return Err((Error::MaxDmaTransferSizeExceeded, self, buffer));
}
let result = unsafe {
self.driver().start_transfer_dma(
bytes_to_read,
0,
&mut buffer,
&mut EmptyBuf,
&mut self.channel,
)
};
if let Err(err) = result {
return Err((err, self, buffer));
}
Ok(SpiDmaTransfer::new(self, buffer, true, false))
}
#[instability::unstable]
#[allow(clippy::type_complexity)]
pub fn transfer<RX, TX>(
mut self,
bytes_to_read: usize,
mut rx_buffer: RX,
bytes_to_write: usize,
mut tx_buffer: TX,
) -> Result<SpiDmaTransfer<'d, Dm, (RX, TX)>, (Error, Self, RX, TX)>
where
RX: DmaRxBuffer,
TX: DmaTxBuffer,
{
if bytes_to_read > MAX_DMA_SIZE || bytes_to_write > MAX_DMA_SIZE {
return Err((
Error::MaxDmaTransferSizeExceeded,
self,
rx_buffer,
tx_buffer,
));
}
let result = unsafe {
self.driver().start_transfer_dma(
bytes_to_read,
bytes_to_write,
&mut rx_buffer,
&mut tx_buffer,
&mut self.channel,
)
};
if let Err(err) = result {
return Err((err, self, rx_buffer, tx_buffer));
}
Ok(SpiDmaTransfer::new(
self,
(rx_buffer, tx_buffer),
true,
true,
))
}
}
#[instability::unstable]
pub struct SpiDmaTransfer<'d, Dm, Buf>
where
Dm: DriverMode,
{
spi_dma: ManuallyDrop<SpiDma<'d, Dm>>,
dma_buf: ManuallyDrop<Buf>,
has_rx: bool,
has_tx: bool,
}
impl<'d, Dm, Buf> SpiDmaTransfer<'d, Dm, Buf>
where
Dm: DriverMode,
{
fn new(spi_dma: SpiDma<'d, Dm>, dma_buf: Buf, has_rx: bool, has_tx: bool) -> Self {
Self {
spi_dma: ManuallyDrop::new(spi_dma),
dma_buf: ManuallyDrop::new(dma_buf),
has_rx,
has_tx,
}
}
#[instability::unstable]
pub fn is_done(&self) -> bool {
if self.has_rx {
let done_int =
enum_set!(DmaRxInterrupt::SuccessfulEof | DmaRxInterrupt::DescriptorEmpty);
if self
.spi_dma
.channel
.rx
.pending_in_interrupts()
.is_disjoint(done_int)
{
return false;
}
}
!self.spi_dma.spi.info().is_bus_busy()
}
#[instability::unstable]
pub fn wait(mut self) -> (SpiDma<'d, Dm>, Buf) {
while !self.is_done() {
}
if self.has_tx {
if !self.spi_dma.channel.tx.is_done() {
self.spi_dma.channel.tx.stop_transfer();
}
}
let retval = unsafe {
(
ManuallyDrop::take(&mut self.spi_dma),
ManuallyDrop::take(&mut self.dma_buf),
)
};
core::mem::forget(self);
retval
}
}
impl<Dm, Buf> Drop for SpiDmaTransfer<'_, Dm, Buf>
where
Dm: DriverMode,
{
fn drop(&mut self) {
while !self.is_done() {
}
unsafe {
ManuallyDrop::drop(&mut self.spi_dma);
ManuallyDrop::drop(&mut self.dma_buf);
}
}
}
struct DmaDriver {
info: &'static Info,
dma_peripheral: crate::dma::DmaPeripheral,
}
impl DmaDriver {
fn regs(&self) -> &RegisterBlock {
self.info.regs()
}
unsafe fn start_transfer_dma<Dm: DriverMode>(
&self,
read_buffer_len: usize,
write_buffer_len: usize,
rx_buffer: &mut impl DmaRxBuffer,
tx_buffer: &mut impl DmaTxBuffer,
channel: &mut Channel<Dm, PeripheralDmaChannel<AnySpi<'_>>>,
) -> Result<(), Error> {
self.enable_dma();
self.info.reset_spi();
if read_buffer_len > 0 {
unsafe {
channel
.rx
.prepare_transfer(self.dma_peripheral, rx_buffer)?;
}
}
if write_buffer_len > 0 {
unsafe {
channel
.tx
.prepare_transfer(self.dma_peripheral, tx_buffer)?;
}
}
#[cfg(esp32)]
self.info
.prepare_length_and_lines(read_buffer_len, write_buffer_len);
self.reset_dma_before_usr_cmd();
#[cfg(not(esp32))]
self.regs()
.dma_conf()
.modify(|_, w| w.dma_slv_seg_trans_en().clear_bit());
self.clear_dma_interrupts();
self.info.setup_for_flush();
self.regs().cmd().modify(|_, w| w.usr().set_bit());
if read_buffer_len > 0 {
channel.rx.start_transfer()?;
}
if write_buffer_len > 0 {
channel.tx.start_transfer()?;
}
Ok(())
}
fn reset_dma_before_usr_cmd(&self) {
#[cfg(dma_kind = "gdma")]
self.regs().dma_conf().modify(|_, w| {
w.rx_afifo_rst().set_bit();
w.buf_afifo_rst().set_bit();
w.dma_afifo_rst().set_bit()
});
}
fn enable_dma(&self) {
#[cfg(dma_kind = "gdma")]
self.regs().dma_conf().modify(|_, w| {
w.dma_tx_ena().set_bit();
w.dma_rx_ena().set_bit();
w.rx_eof_en().clear_bit()
});
#[cfg(dma_kind = "pdma")]
{
fn set_rst_bit(reg_block: &RegisterBlock, bit: bool) {
reg_block.dma_conf().modify(|_, w| {
w.in_rst().bit(bit);
w.out_rst().bit(bit);
w.ahbm_fifo_rst().bit(bit);
w.ahbm_rst().bit(bit)
});
#[cfg(esp32s2)]
reg_block
.dma_conf()
.modify(|_, w| w.dma_infifo_full_clr().bit(bit));
}
set_rst_bit(self.regs(), true);
set_rst_bit(self.regs(), false);
}
}
fn clear_dma_interrupts(&self) {
#[cfg(dma_kind = "gdma")]
self.regs().dma_int_clr().write(|w| {
w.dma_infifo_full_err().clear_bit_by_one();
w.dma_outfifo_empty_err().clear_bit_by_one();
w.trans_done().clear_bit_by_one();
w.mst_rx_afifo_wfull_err().clear_bit_by_one();
w.mst_tx_afifo_rempty_err().clear_bit_by_one()
});
#[cfg(dma_kind = "pdma")]
self.regs().dma_int_clr().write(|w| {
w.inlink_dscr_empty().clear_bit_by_one();
w.outlink_dscr_error().clear_bit_by_one();
w.inlink_dscr_error().clear_bit_by_one();
w.in_done().clear_bit_by_one();
w.in_err_eof().clear_bit_by_one();
w.in_suc_eof().clear_bit_by_one();
w.out_done().clear_bit_by_one();
w.out_eof().clear_bit_by_one();
w.out_total_eof().clear_bit_by_one()
});
}
}
#[doc(hidden)]
#[allow(private_bounds)]
pub trait InstanceDma: Instance + DmaEligible {}
impl<'d> DmaEligible for AnySpi<'d> {
#[cfg(dma_kind = "gdma")]
type Dma = crate::dma::AnyGdmaChannel<'d>;
#[cfg(dma_kind = "pdma")]
type Dma = crate::dma::AnySpiDmaChannel<'d>;
fn dma_peripheral(&self) -> crate::dma::DmaPeripheral {
match &self.0 {
#[cfg(spi_master_spi2)]
any::Inner::Spi2(_) => crate::dma::DmaPeripheral::Spi2,
#[cfg(spi_master_spi3)]
any::Inner::Spi3(_) => crate::dma::DmaPeripheral::Spi3,
}
}
}
#[cfg(soc_has_spi2)]
impl InstanceDma for crate::peripherals::SPI2<'_> {}
#[cfg(soc_has_spi3)]
impl InstanceDma for crate::peripherals::SPI3<'_> {}
impl InstanceDma for AnySpi<'_> {}
}
pub trait Instance: crate::private::Sealed + any::Degrade {
#[doc(hidden)]
fn info(&self) -> &'static Info;
}
#[non_exhaustive]
#[doc(hidden)]
pub struct Info {
pub register_block: *const RegisterBlock,
pub peripheral: crate::system::Peripheral,
pub sclk: InputSignal,
pub mosi: InputSignal,
pub miso: OutputSignal,
pub cs: InputSignal,
}
impl Info {
#[instability::unstable]
pub fn regs(&self) -> &RegisterBlock {
unsafe { &*self.register_block }
}
fn reset_spi(&self) {
#[cfg(esp32)]
{
self.regs().slave().modify(|_, w| w.sync_reset().set_bit());
self.regs()
.slave()
.modify(|_, w| w.sync_reset().clear_bit());
}
#[cfg(not(esp32))]
{
self.regs().slave().modify(|_, w| w.soft_reset().set_bit());
self.regs()
.slave()
.modify(|_, w| w.soft_reset().clear_bit());
}
}
#[cfg(esp32)]
fn prepare_length_and_lines(&self, rx_len: usize, tx_len: usize) {
self.regs()
.slv_rdbuf_dlen()
.write(|w| unsafe { w.bits((rx_len as u32 * 8).saturating_sub(1)) });
self.regs()
.slv_wrbuf_dlen()
.write(|w| unsafe { w.bits((tx_len as u32 * 8).saturating_sub(1)) });
self.regs().user().modify(|_, w| {
w.usr_mosi().bit(rx_len > 0);
w.usr_miso().bit(tx_len > 0)
});
}
fn init(&self) {
self.regs().clock().write(|w| unsafe { w.bits(0) });
self.regs().user().write(|w| unsafe { w.bits(0) });
self.regs().ctrl().write(|w| unsafe { w.bits(0) });
self.regs().slave().write(|w| {
#[cfg(esp32)]
w.slv_wr_rd_buf_en().set_bit();
w.mode().set_bit()
});
self.reset_spi();
self.regs().user().modify(|_, w| {
w.doutdin().set_bit();
w.sio().clear_bit()
});
#[cfg(not(esp32))]
self.regs().misc().write(|w| unsafe { w.bits(0) });
}
fn set_data_mode(&self, data_mode: Mode, dma: bool) {
#[cfg(esp32)]
{
self.regs().pin().modify(|_, w| {
w.ck_idle_edge()
.bit(matches!(data_mode, Mode::_0 | Mode::_1))
});
self.regs()
.user()
.modify(|_, w| w.ck_i_edge().bit(matches!(data_mode, Mode::_1 | Mode::_2)));
self.regs().ctrl2().modify(|_, w| unsafe {
match data_mode {
Mode::_0 => {
w.miso_delay_mode().bits(0);
w.miso_delay_num().bits(0);
w.mosi_delay_mode().bits(2);
w.mosi_delay_num().bits(2)
}
Mode::_1 => {
w.miso_delay_mode().bits(2);
w.miso_delay_num().bits(0);
w.mosi_delay_mode().bits(0);
w.mosi_delay_num().bits(0)
}
Mode::_2 => {
w.miso_delay_mode().bits(0);
w.miso_delay_num().bits(0);
w.mosi_delay_mode().bits(1);
w.mosi_delay_num().bits(2)
}
Mode::_3 => {
w.miso_delay_mode().bits(1);
w.miso_delay_num().bits(0);
w.mosi_delay_mode().bits(0);
w.mosi_delay_num().bits(0)
}
}
});
if dma {
assert!(
matches!(data_mode, Mode::_1 | Mode::_3),
"Mode {:?} is not supported with DMA",
data_mode
);
}
}
#[cfg(not(esp32))]
{
_ = dma;
self.regs().user().modify(|_, w| {
w.tsck_i_edge()
.bit(matches!(data_mode, Mode::_1 | Mode::_2));
w.rsck_i_edge()
.bit(matches!(data_mode, Mode::_1 | Mode::_2))
});
cfg_if::cfg_if! {
if #[cfg(esp32s2)] {
let ctrl1_reg = self.regs().ctrl1();
} else {
let ctrl1_reg = self.regs().slave();
}
}
ctrl1_reg.modify(|_, w| {
w.clk_mode_13()
.bit(matches!(data_mode, Mode::_1 | Mode::_3))
});
}
}
#[cfg(spi_slave_supports_dma)]
fn is_bus_busy(&self) -> bool {
#[cfg(dma_kind = "pdma")]
{
self.regs().slave().read().trans_done().bit_is_clear()
}
#[cfg(dma_kind = "gdma")]
{
self.regs().dma_int_raw().read().trans_done().bit_is_clear()
}
}
#[cfg(spi_slave_supports_dma)]
fn setup_for_flush(&self) {
#[cfg(dma_kind = "pdma")]
self.regs()
.slave()
.modify(|_, w| w.trans_done().clear_bit());
#[cfg(dma_kind = "gdma")]
self.regs()
.dma_int_clr()
.write(|w| w.trans_done().clear_bit_by_one());
}
}
impl PartialEq for Info {
fn eq(&self, other: &Self) -> bool {
core::ptr::eq(self.register_block, other.register_block)
}
}
unsafe impl Sync for Info {}
for_each_spi_slave! {
($peri:ident, $sys:ident, $sclk:ident, $mosi:ident, $miso:ident, $cs:ident) => {
impl Instance for crate::peripherals::$peri<'_> {
#[inline(always)]
fn info(&self) -> &'static Info {
static INFO: Info = Info {
register_block: crate::peripherals::$peri::regs(),
peripheral: crate::system::Peripheral::$sys,
sclk: InputSignal::$sclk,
mosi: InputSignal::$mosi,
miso: OutputSignal::$miso,
cs: InputSignal::$cs,
};
&INFO
}
}
};
}
crate::any_peripheral! {
pub peripheral AnySpi<'d> {
#[cfg(spi_master_spi2)]
Spi2(crate::peripherals::SPI2<'d>),
#[cfg(spi_master_spi3)]
Spi3(crate::peripherals::SPI3<'d>),
}
}
impl Instance for AnySpi<'_> {
fn info(&self) -> &'static Info {
any::delegate!(self, spi => { spi.info() })
}
}