use core::sync::atomic::AtomicBool;
use core::sync::atomic::Ordering;
use core::task::Waker;
use esp_hal::handler;
use esp_hal::interrupt::InterruptHandler;
use esp_hal::interrupt::Priority;
#[cfg(feature = "iram")]
use esp_hal::ram;
use esp_sync::NonReentrantMutex;
use crate::Hub75;
use crate::Hub75Error;
use crate::Hub75Swap;
#[cfg(feature = "full-chain-dma")]
pub(crate) use crate::bcm::full_chain::BcmBuf;
#[cfg(not(feature = "full-chain-dma"))]
pub(crate) use crate::bcm::linear::BcmBuf;
use crate::framebuffer::FrameBuffer;
#[cfg(hub75_use_lcd_cam)]
use crate::framebuffer::WordSize;
pub(crate) type Shared<T> = NonReentrantMutex<T>;
pub(crate) fn handler_with_priority(
handler: InterruptHandler,
priority: Option<Priority>,
) -> InterruptHandler {
match priority {
Some(prio) => InterruptHandler::new(handler.handler().callback(), prio),
None => handler,
}
}
pub(crate) static SWAP_DONE: AtomicBool = AtomicBool::new(false);
static DRIVER_TAKEN: AtomicBool = AtomicBool::new(false);
pub(crate) static SWAP_WAKER: Shared<Option<Waker>> = Shared::new(None);
pub(crate) static HAS_ERROR: AtomicBool = AtomicBool::new(false);
mod transfer;
pub(crate) use transfer::Transfer;
pub(crate) use transfer::TxDriver;
pub(crate) fn start_internal(fb: &'static impl FrameBuffer) -> Result<(), Hub75Error> {
crate::bcm::validate_fb_internal_ram(fb);
let mut wake = None;
let result = STATE.with(|state| {
let state = state.as_mut().expect("Hub75 not initialized");
if !state.transfer.is_idle() {
return Err(Hub75Error::AlreadyRunning);
}
SWAP_DONE.store(true, Ordering::Release);
if wake.is_none() {
wake = SWAP_WAKER.with(Option::take);
}
cfg_select! {
feature = "full-chain-dma" => {
let (descriptors, descriptor_count) = {
let buf = state.transfer.buf_mut();
buf.build(fb);
(buf.descriptors_ptr(), buf.descriptor_count())
};
state.descriptors = descriptors;
state.descriptor_count = descriptor_count;
}
_ => {
state.transfer.buf_mut().bind_cache(fb);
}
}
state.current_fb_ptr = core::ptr::from_ref(fb).cast::<()>();
state.pending_delta = None;
match state.transfer.start() {
Ok(()) => {
HAS_ERROR.store(false, Ordering::Release);
SWAP_DONE.store(false, Ordering::Release);
Ok(())
}
Err(err) => {
HAS_ERROR.store(true, Ordering::Release);
Err(err)
}
}
});
if let Some(waker) = wake {
waker.wake();
}
result
}
pub(crate) fn claim_driver() -> Result<(), Hub75Error> {
if DRIVER_TAKEN.swap(true, Ordering::AcqRel) {
Err(Hub75Error::AlreadyInitialised)
} else {
Ok(())
}
}
pub(crate) struct State {
pub(crate) transfer: Transfer,
#[cfg(feature = "full-chain-dma")]
pub(crate) descriptors: *mut esp_hal::dma::DmaDescriptor,
#[cfg(feature = "full-chain-dma")]
pub(crate) descriptor_count: usize,
pub(crate) current_fb_ptr: *const (),
pub(crate) pending_delta: Option<isize>,
}
unsafe impl Send for State {}
pub(crate) type SharedState = Shared<Option<State>>;
pub(crate) static STATE: SharedState = Shared::new(None);
pub(crate) fn init_state(tx: TxDriver, buf: BcmBuf, #[cfg(hub75_use_lcd_cam)] word_size: WordSize) {
STATE.with(|state| {
*state = Some(State {
transfer: Transfer::new(
tx,
buf,
#[cfg(hub75_use_lcd_cam)]
word_size,
),
#[cfg(feature = "full-chain-dma")]
descriptors: core::ptr::null_mut(),
#[cfg(feature = "full-chain-dma")]
descriptor_count: 0,
current_fb_ptr: core::ptr::null(),
pending_delta: None,
});
});
}
#[handler]
#[cfg_attr(feature = "iram", ram)]
pub(crate) fn isr() {
let mut wake = None;
STATE.with(|state| {
let Some(state) = state.as_mut() else { return };
#[cfg(feature = "circular-dma")]
if state.pending_delta.is_none() {
Transfer::clear_frame_interrupt();
return;
}
if !state.transfer.is_in_flight() {
return;
}
if state.transfer.finish().is_err() {
HAS_ERROR.store(true, Ordering::Release);
SWAP_DONE.store(true, Ordering::Release);
if wake.is_none() {
wake = SWAP_WAKER.with(Option::take);
}
return;
}
let frame_boundary = cfg_select! {
feature = "full-chain-dma" => true,
_ => state.transfer.buf_mut().advance(),
};
if frame_boundary && let Some(delta) = state.pending_delta.take() {
#[cfg(feature = "full-chain-dma")]
crate::bcm::full_chain::apply_delta(state.descriptors, state.descriptor_count, delta);
#[cfg(not(feature = "full-chain-dma"))]
state.transfer.buf_mut().apply_delta(delta);
#[cfg(not(feature = "circular-dma"))]
{
SWAP_DONE.store(true, Ordering::Release);
if wake.is_none() {
wake = SWAP_WAKER.with(Option::take);
}
}
}
#[cfg(feature = "circular-dma")]
crate::bcm::full_chain::disarm_boundary(state.descriptors, state.descriptor_count);
if state.transfer.start().is_err() {
HAS_ERROR.store(true, Ordering::Release);
#[cfg(not(feature = "circular-dma"))]
{
SWAP_DONE.store(true, Ordering::Release);
if wake.is_none() {
wake = SWAP_WAKER.with(Option::take);
}
}
}
#[cfg(feature = "circular-dma")]
{
SWAP_DONE.store(true, Ordering::Release);
if wake.is_none() {
wake = SWAP_WAKER.with(Option::take);
}
}
});
if let Some(waker) = wake {
waker.wake();
}
}
impl<FB: FrameBuffer + 'static> Hub75Swap<FB> {
pub fn is_done(&self) -> bool {
SWAP_DONE.load(Ordering::Acquire) || HAS_ERROR.load(Ordering::Acquire)
}
pub fn wait(self) -> Result<&'static mut FB, (Hub75Error, &'static mut FB)> {
loop {
if HAS_ERROR.load(Ordering::Acquire) {
return STATE.with(|state| {
let state = state.as_mut().unwrap();
state.pending_delta = None;
let err = state
.transfer
.error()
.unwrap_or(Hub75Error::Dma(esp_hal::dma::DmaError::DescriptorError));
Err((err, unsafe { &mut *self.new_fb_ptr }))
});
}
if SWAP_DONE.load(Ordering::Acquire) {
break;
}
core::hint::spin_loop();
}
Ok(unsafe { &mut *self.old_fb_ptr })
}
pub async fn wait_for_done(&mut self) {
core::future::poll_fn(|cx| {
if SWAP_DONE.load(Ordering::Acquire) || HAS_ERROR.load(Ordering::Acquire) {
return core::task::Poll::Ready(());
}
SWAP_WAKER.with(|waker| {
if SWAP_DONE.load(Ordering::Acquire) || HAS_ERROR.load(Ordering::Acquire) {
return core::task::Poll::Ready(());
}
*waker = Some(cx.waker().clone());
core::task::Poll::Pending
})
})
.await;
}
}
impl<DM: esp_hal::DriverMode, FB: FrameBuffer + 'static> Hub75<DM, FB> {
pub fn swap(
&self,
new_fb: &'static mut FB,
) -> Result<Hub75Swap<FB>, (Hub75Error, &'static mut FB)> {
#[cfg(not(feature = "full-chain-dma"))]
{
let count = new_fb.bcm_segment_count();
let spg = new_fb.bcm_segments_per_group();
assert!(
count <= crate::bcm::linear::MAX_SEGMENTS,
"bcm_segment_count {count} exceeds MAX_SEGMENTS"
);
assert!(
spg > 0 && count.is_multiple_of(spg),
"bcm_segment_count {count} not divisible by segments_per_group {spg}"
);
}
let new_fb_ptr = core::ptr::from_mut::<FB>(new_fb);
let old_fb_ptr = STATE.with(|state| {
let state = state.as_mut().expect("Hub75 not initialized");
if state.pending_delta.is_some() {
return Err(new_fb_ptr as *const ());
}
let old = state.current_fb_ptr;
let delta = new_fb_ptr as isize - old as isize;
state.pending_delta = Some(delta);
#[cfg(feature = "circular-dma")]
crate::bcm::full_chain::arm_boundary(state.descriptors, state.descriptor_count);
state.current_fb_ptr = new_fb_ptr as *const ();
SWAP_DONE.store(false, Ordering::Release);
Ok(old)
});
match old_fb_ptr {
Ok(old) => Ok(Hub75Swap {
old_fb_ptr: old as *mut FB,
new_fb_ptr,
}),
Err(_) => Err((Hub75Error::SwapInFlight, new_fb)),
}
}
}
#[cfg(not(feature = "circular-dma"))]
impl<DM: esp_hal::DriverMode, FB: FrameBuffer + 'static> Hub75<DM, FB> {
pub fn restart(&self, fb: &'static FB) -> Result<(), Hub75Error> {
start_internal(fb)
}
}