281 lines
8.6 KiB
Rust
281 lines
8.6 KiB
Rust
use core::future::poll_fn;
|
|
use core::sync::atomic::{compiler_fence, Ordering};
|
|
use core::task::Poll;
|
|
|
|
use self::sealed::Instance;
|
|
use crate::interrupt;
|
|
use crate::interrupt::typelevel::Interrupt;
|
|
use crate::peripherals::IPCC;
|
|
use crate::rcc::sealed::RccPeripheral;
|
|
|
|
/// Interrupt handler.
|
|
pub struct ReceiveInterruptHandler {}
|
|
|
|
impl interrupt::typelevel::Handler<interrupt::typelevel::IPCC_C1_RX> for ReceiveInterruptHandler {
|
|
unsafe fn on_interrupt() {
|
|
let regs = IPCC::regs();
|
|
|
|
let channels = [
|
|
IpccChannel::Channel1,
|
|
IpccChannel::Channel2,
|
|
IpccChannel::Channel3,
|
|
IpccChannel::Channel4,
|
|
IpccChannel::Channel5,
|
|
IpccChannel::Channel6,
|
|
];
|
|
|
|
// Status register gives channel occupied status. For rx, use cpu1.
|
|
let sr = regs.cpu(1).sr().read();
|
|
regs.cpu(0).mr().modify(|w| {
|
|
for channel in channels {
|
|
if sr.chf(channel as usize) {
|
|
// If bit is set to 1 then interrupt is disabled; we want to disable the interrupt
|
|
w.set_chom(channel as usize, true);
|
|
|
|
// There shouldn't be a race because the channel is masked only if the interrupt has fired
|
|
IPCC::state().rx_waker_for(channel).wake();
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
pub struct TransmitInterruptHandler {}
|
|
|
|
impl interrupt::typelevel::Handler<interrupt::typelevel::IPCC_C1_TX> for TransmitInterruptHandler {
|
|
unsafe fn on_interrupt() {
|
|
let regs = IPCC::regs();
|
|
|
|
let channels = [
|
|
IpccChannel::Channel1,
|
|
IpccChannel::Channel2,
|
|
IpccChannel::Channel3,
|
|
IpccChannel::Channel4,
|
|
IpccChannel::Channel5,
|
|
IpccChannel::Channel6,
|
|
];
|
|
|
|
// Status register gives channel occupied status. For tx, use cpu0.
|
|
let sr = regs.cpu(0).sr().read();
|
|
regs.cpu(0).mr().modify(|w| {
|
|
for channel in channels {
|
|
if !sr.chf(channel as usize) {
|
|
// If bit is set to 1 then interrupt is disabled; we want to disable the interrupt
|
|
w.set_chfm(channel as usize, true);
|
|
|
|
// There shouldn't be a race because the channel is masked only if the interrupt has fired
|
|
IPCC::state().tx_waker_for(channel).wake();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
#[non_exhaustive]
|
|
#[derive(Clone, Copy, Default)]
|
|
pub struct Config {
|
|
// TODO: add IPCC peripheral configuration, if any, here
|
|
// reserved for future use
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
#[repr(C)]
|
|
pub enum IpccChannel {
|
|
Channel1 = 0,
|
|
Channel2 = 1,
|
|
Channel3 = 2,
|
|
Channel4 = 3,
|
|
Channel5 = 4,
|
|
Channel6 = 5,
|
|
}
|
|
|
|
pub struct Ipcc;
|
|
|
|
impl Ipcc {
|
|
pub fn enable(_config: Config) {
|
|
IPCC::enable();
|
|
IPCC::reset();
|
|
IPCC::set_cpu2(true);
|
|
|
|
_configure_pwr();
|
|
|
|
let regs = IPCC::regs();
|
|
|
|
regs.cpu(0).cr().modify(|w| {
|
|
w.set_rxoie(true);
|
|
w.set_txfie(true);
|
|
});
|
|
|
|
// enable interrupts
|
|
crate::interrupt::typelevel::IPCC_C1_RX::unpend();
|
|
crate::interrupt::typelevel::IPCC_C1_TX::unpend();
|
|
|
|
unsafe { crate::interrupt::typelevel::IPCC_C1_RX::enable() };
|
|
unsafe { crate::interrupt::typelevel::IPCC_C1_TX::enable() };
|
|
}
|
|
|
|
/// Send data to an IPCC channel. The closure is called to write the data when appropriate.
|
|
pub async fn send(channel: IpccChannel, f: impl FnOnce()) {
|
|
let regs = IPCC::regs();
|
|
|
|
Self::flush(channel).await;
|
|
|
|
f();
|
|
|
|
compiler_fence(Ordering::SeqCst);
|
|
|
|
trace!("ipcc: ch {}: send data", channel as u8);
|
|
regs.cpu(0).scr().write(|w| w.set_chs(channel as usize, true));
|
|
}
|
|
|
|
/// Wait for the tx channel to become clear
|
|
pub async fn flush(channel: IpccChannel) {
|
|
let regs = IPCC::regs();
|
|
|
|
// This is a race, but is nice for debugging
|
|
if regs.cpu(0).sr().read().chf(channel as usize) {
|
|
trace!("ipcc: ch {}: wait for tx free", channel as u8);
|
|
}
|
|
|
|
poll_fn(|cx| {
|
|
IPCC::state().tx_waker_for(channel).register(cx.waker());
|
|
// If bit is set to 1 then interrupt is disabled; we want to enable the interrupt
|
|
regs.cpu(0).mr().modify(|w| w.set_chfm(channel as usize, false));
|
|
|
|
compiler_fence(Ordering::SeqCst);
|
|
|
|
if !regs.cpu(0).sr().read().chf(channel as usize) {
|
|
// If bit is set to 1 then interrupt is disabled; we want to disable the interrupt
|
|
regs.cpu(0).mr().modify(|w| w.set_chfm(channel as usize, true));
|
|
|
|
Poll::Ready(())
|
|
} else {
|
|
Poll::Pending
|
|
}
|
|
})
|
|
.await;
|
|
}
|
|
|
|
/// Receive data from an IPCC channel. The closure is called to read the data when appropriate.
|
|
pub async fn receive<R>(channel: IpccChannel, mut f: impl FnMut() -> Option<R>) -> R {
|
|
let regs = IPCC::regs();
|
|
|
|
loop {
|
|
// This is a race, but is nice for debugging
|
|
if !regs.cpu(1).sr().read().chf(channel as usize) {
|
|
trace!("ipcc: ch {}: wait for rx occupied", channel as u8);
|
|
}
|
|
|
|
poll_fn(|cx| {
|
|
IPCC::state().rx_waker_for(channel).register(cx.waker());
|
|
// If bit is set to 1 then interrupt is disabled; we want to enable the interrupt
|
|
regs.cpu(0).mr().modify(|w| w.set_chom(channel as usize, false));
|
|
|
|
compiler_fence(Ordering::SeqCst);
|
|
|
|
if regs.cpu(1).sr().read().chf(channel as usize) {
|
|
// If bit is set to 1 then interrupt is disabled; we want to disable the interrupt
|
|
regs.cpu(0).mr().modify(|w| w.set_chfm(channel as usize, true));
|
|
|
|
Poll::Ready(())
|
|
} else {
|
|
Poll::Pending
|
|
}
|
|
})
|
|
.await;
|
|
|
|
trace!("ipcc: ch {}: read data", channel as u8);
|
|
|
|
match f() {
|
|
Some(ret) => return ret,
|
|
None => {}
|
|
}
|
|
|
|
trace!("ipcc: ch {}: clear rx", channel as u8);
|
|
compiler_fence(Ordering::SeqCst);
|
|
// If the channel is clear and the read function returns none, fetch more data
|
|
regs.cpu(0).scr().write(|w| w.set_chc(channel as usize, true));
|
|
}
|
|
}
|
|
}
|
|
|
|
impl sealed::Instance for crate::peripherals::IPCC {
|
|
fn regs() -> crate::pac::ipcc::Ipcc {
|
|
crate::pac::IPCC
|
|
}
|
|
|
|
fn set_cpu2(enabled: bool) {
|
|
crate::pac::PWR.cr4().modify(|w| w.set_c2boot(enabled));
|
|
}
|
|
|
|
fn state() -> &'static self::sealed::State {
|
|
static STATE: self::sealed::State = self::sealed::State::new();
|
|
&STATE
|
|
}
|
|
}
|
|
|
|
pub(crate) mod sealed {
|
|
use embassy_sync::waitqueue::AtomicWaker;
|
|
|
|
use super::*;
|
|
|
|
pub struct State {
|
|
rx_wakers: [AtomicWaker; 6],
|
|
tx_wakers: [AtomicWaker; 6],
|
|
}
|
|
|
|
impl State {
|
|
pub const fn new() -> Self {
|
|
const WAKER: AtomicWaker = AtomicWaker::new();
|
|
|
|
Self {
|
|
rx_wakers: [WAKER; 6],
|
|
tx_wakers: [WAKER; 6],
|
|
}
|
|
}
|
|
|
|
pub const fn rx_waker_for(&self, channel: IpccChannel) -> &AtomicWaker {
|
|
match channel {
|
|
IpccChannel::Channel1 => &self.rx_wakers[0],
|
|
IpccChannel::Channel2 => &self.rx_wakers[1],
|
|
IpccChannel::Channel3 => &self.rx_wakers[2],
|
|
IpccChannel::Channel4 => &self.rx_wakers[3],
|
|
IpccChannel::Channel5 => &self.rx_wakers[4],
|
|
IpccChannel::Channel6 => &self.rx_wakers[5],
|
|
}
|
|
}
|
|
|
|
pub const fn tx_waker_for(&self, channel: IpccChannel) -> &AtomicWaker {
|
|
match channel {
|
|
IpccChannel::Channel1 => &self.tx_wakers[0],
|
|
IpccChannel::Channel2 => &self.tx_wakers[1],
|
|
IpccChannel::Channel3 => &self.tx_wakers[2],
|
|
IpccChannel::Channel4 => &self.tx_wakers[3],
|
|
IpccChannel::Channel5 => &self.tx_wakers[4],
|
|
IpccChannel::Channel6 => &self.tx_wakers[5],
|
|
}
|
|
}
|
|
}
|
|
|
|
pub trait Instance: crate::rcc::RccPeripheral {
|
|
fn regs() -> crate::pac::ipcc::Ipcc;
|
|
fn set_cpu2(enabled: bool);
|
|
fn state() -> &'static State;
|
|
}
|
|
}
|
|
|
|
fn _configure_pwr() {
|
|
// TODO: move the rest of this to rcc
|
|
let rcc = crate::pac::RCC;
|
|
|
|
// TODO: required
|
|
// set RF wake-up clock = LSE
|
|
rcc.csr().modify(|w| w.set_rfwkpsel(0b01));
|
|
|
|
// set LPTIM1 & LPTIM2 clock source
|
|
rcc.ccipr().modify(|w| {
|
|
w.set_lptim1sel(0b00); // PCLK
|
|
w.set_lptim2sel(0b00); // PCLK
|
|
});
|
|
}
|