use core::cell::RefCell;
use core::sync::atomic::AtomicBool;
use core::sync::atomic::AtomicU32;
use core::sync::atomic::Ordering;
use core::task::Waker;
use critical_section::Mutex;
use esp_hal::handler;
#[cfg(feature = "iram")]
use esp_hal::ram;
use esp_hal::Blocking;
#[cfg(not(feature = "circular-dma"))]
use crate::bcm::linear::BcmBuf;
#[cfg(not(feature = "circular-dma"))]
use crate::bcm::planes_from_fb;
#[cfg(not(feature = "circular-dma"))]
use crate::bcm::PlaneInfo;
#[cfg(feature = "circular-dma")]
use crate::bcm::circular::CircularBcmBuf;
use crate::framebuffer::FrameBuffer;
#[cfg(all(hub75_use_lcd_cam, not(feature = "circular-dma")))]
use crate::framebuffer::WordSize;
use crate::Hub75Error;
#[cfg(all(hub75_use_i2s_parallel, not(feature = "circular-dma")))]
pub(crate) type TxDriver = esp_hal::i2s::parallel::I2sParallel<'static, Blocking>;
#[cfg(all(hub75_use_i2s_parallel, not(feature = "circular-dma")))]
pub(crate) type TxTransfer = esp_hal::i2s::parallel::I2sParallelTransfer<'static, BcmBuf, Blocking>;
#[cfg(all(hub75_use_i2s_parallel, feature = "circular-dma"))]
pub(crate) type TxTransfer =
esp_hal::i2s::parallel::I2sParallelTransfer<'static, CircularBcmBuf, Blocking>;
#[cfg(all(hub75_use_parl_io, not(feature = "circular-dma")))]
pub(crate) type TxDriver = esp_hal::parl_io::ParlIoTx<'static, Blocking>;
#[cfg(all(hub75_use_parl_io, not(feature = "circular-dma")))]
pub(crate) type TxTransfer = esp_hal::parl_io::ParlIoTxTransfer<'static, BcmBuf, Blocking>;
#[cfg(all(hub75_use_parl_io, feature = "circular-dma"))]
pub(crate) type TxTransfer =
esp_hal::parl_io::ParlIoTxTransfer<'static, CircularBcmBuf, Blocking>;
#[cfg(all(hub75_use_lcd_cam, not(feature = "circular-dma")))]
pub(crate) type TxDriver = esp_hal::lcd_cam::lcd::i8080::I8080<'static, Blocking>;
#[cfg(all(hub75_use_lcd_cam, not(feature = "circular-dma")))]
pub(crate) type TxTransfer =
esp_hal::lcd_cam::lcd::i8080::I8080Transfer<'static, BcmBuf, Blocking>;
#[cfg(all(hub75_use_lcd_cam, feature = "circular-dma"))]
pub(crate) type TxTransfer =
esp_hal::lcd_cam::lcd::i8080::I8080Transfer<'static, CircularBcmBuf, Blocking>;
#[cfg(not(feature = "circular-dma"))]
pub(crate) enum TransferPhase {
Idle(TxDriver, BcmBuf),
InFlight(TxTransfer),
Error(Hub75Error, TxDriver, BcmBuf),
Transitioning,
}
#[cfg(not(feature = "circular-dma"))]
pub(crate) struct IsrState {
pub(crate) transfer: TransferPhase,
#[cfg(hub75_use_lcd_cam)]
pub(crate) word_size: crate::framebuffer::WordSize,
pub(crate) current_fb_ptr: *const (),
pub(crate) pending_planes: Option<PlaneInfo>,
pub(crate) pending_fb_ptr: *const (),
}
#[cfg(not(feature = "circular-dma"))]
unsafe impl Send for IsrState {}
#[cfg(not(feature = "circular-dma"))]
type SharedIsrState = Mutex<RefCell<Option<IsrState>>>;
#[cfg(not(feature = "circular-dma"))]
static ISR_STATE: SharedIsrState = Mutex::new(RefCell::new(None));
#[cfg(feature = "circular-dma")]
pub(crate) struct CircularState {
#[allow(dead_code)] pub(crate) transfer: Option<TxTransfer>,
pub(crate) descriptors: *mut esp_hal::dma::DmaDescriptor,
pub(crate) desc_count: usize,
pub(crate) current_fb_ptr: *const (),
}
#[cfg(feature = "circular-dma")]
unsafe impl Send for CircularState {}
#[cfg(feature = "circular-dma")]
type SharedCircularState = Mutex<RefCell<Option<CircularState>>>;
#[cfg(feature = "circular-dma")]
static CIRCULAR_STATE: SharedCircularState = Mutex::new(RefCell::new(None));
static SWAP_DONE: AtomicBool = AtomicBool::new(false);
#[cfg(not(feature = "circular-dma"))]
static HAS_ERROR: AtomicBool = AtomicBool::new(false);
static SWAP_WAKER: Mutex<RefCell<Option<Waker>>> = Mutex::new(RefCell::new(None));
static FRAME_COUNT: AtomicU32 = AtomicU32::new(0);
#[cfg(feature = "circular-dma")]
#[allow(clippy::type_complexity)]
static CLEAR_INTERRUPT: Mutex<RefCell<Option<fn()>>> = Mutex::new(RefCell::new(None));
fn signal_swap_done(cs: critical_section::CriticalSection) {
SWAP_DONE.store(true, Ordering::Release);
if let Some(waker) = SWAP_WAKER.borrow_ref_mut(cs).take() {
waker.wake();
}
}
#[cfg(all(hub75_use_i2s_parallel, not(feature = "circular-dma")))]
#[cfg_attr(feature = "iram", ram)]
fn start_transfer(tx: TxDriver, buf: BcmBuf) -> Result<TxTransfer, (Hub75Error, TxDriver, BcmBuf)> {
tx.send(buf)
.map_err(|(err, tx, buf)| (Hub75Error::Dma(err), tx, buf))
}
#[cfg(all(hub75_use_i2s_parallel, not(feature = "circular-dma")))]
#[cfg_attr(feature = "iram", ram)]
fn finish_transfer(xfer: TxTransfer) -> (Result<(), Hub75Error>, TxDriver, BcmBuf) {
let (tx, buf) = xfer.wait();
(Ok(()), tx, buf)
}
#[cfg(all(hub75_use_parl_io, not(feature = "circular-dma"), esp32c5))]
const PARL_IO_DUMMY_TRANSFER_LEN: usize = 0;
#[cfg(all(hub75_use_parl_io, not(feature = "circular-dma")))]
#[cfg_attr(feature = "iram", ram)]
fn start_transfer(tx: TxDriver, buf: BcmBuf) -> Result<TxTransfer, (Hub75Error, TxDriver, BcmBuf)> {
#[cfg(esp32c5)]
let transfer_len = PARL_IO_DUMMY_TRANSFER_LEN;
#[cfg(not(esp32c5))]
let transfer_len = buf.current_transfer_len();
tx.write(transfer_len, buf)
.map_err(|(err, tx, buf)| (Hub75Error::ParlIo(err), tx, buf))
}
#[cfg(all(hub75_use_parl_io, not(feature = "circular-dma")))]
#[cfg_attr(feature = "iram", ram)]
fn finish_transfer(xfer: TxTransfer) -> (Result<(), Hub75Error>, TxDriver, BcmBuf) {
let (result, tx, buf) = xfer.wait();
(result.map_err(Hub75Error::Dma), tx, buf)
}
#[cfg(all(hub75_use_lcd_cam, not(feature = "circular-dma")))]
#[cfg_attr(feature = "iram", ram)]
fn start_transfer(
tx: TxDriver,
buf: BcmBuf,
word_size: WordSize,
) -> Result<TxTransfer, (Hub75Error, TxDriver, BcmBuf)> {
use esp_hal::lcd_cam::lcd::i8080::Command;
let result = match word_size {
WordSize::Eight => tx.send(Command::<u8>::None, 0, buf),
WordSize::Sixteen => tx.send(Command::<u16>::None, 0, buf),
};
result.map_err(|(err, tx, buf)| (Hub75Error::Dma(err), tx, buf))
}
#[cfg(all(hub75_use_lcd_cam, not(feature = "circular-dma")))]
#[cfg_attr(feature = "iram", ram)]
fn finish_transfer(xfer: TxTransfer) -> (Result<(), Hub75Error>, TxDriver, BcmBuf) {
let (result, tx, buf) = xfer.wait();
(result.map_err(Hub75Error::Dma), tx, buf)
}
#[cfg(not(feature = "circular-dma"))]
#[handler]
#[cfg_attr(feature = "iram", ram)]
pub(crate) fn hub75_isr() {
critical_section::with(|cs| {
let mut borrow = ISR_STATE.borrow_ref_mut(cs);
let state = match borrow.as_mut() {
Some(s) => s,
None => return,
};
let xfer = match core::mem::replace(&mut state.transfer, TransferPhase::Transitioning) {
TransferPhase::InFlight(xfer) => xfer,
other => {
state.transfer = other;
return;
}
};
let (result, tx, mut buf) = finish_transfer(xfer);
if let Err(err) = result {
state.transfer = TransferPhase::Error(err, tx, buf);
HAS_ERROR.store(true, Ordering::Release);
signal_swap_done(cs);
return;
}
let frame_boundary = buf.advance();
if frame_boundary {
FRAME_COUNT.fetch_add(1, Ordering::Relaxed);
if let Some(pending) = state.pending_planes.take() {
state.current_fb_ptr = state.pending_fb_ptr;
state.pending_fb_ptr = core::ptr::null();
buf.update_planes(pending);
signal_swap_done(cs);
}
}
#[cfg(hub75_use_lcd_cam)]
let xfer_result = start_transfer(tx, buf, state.word_size);
#[cfg(not(hub75_use_lcd_cam))]
let xfer_result = start_transfer(tx, buf);
match xfer_result {
Ok(new_xfer) => {
state.transfer = TransferPhase::InFlight(new_xfer);
}
Err((hub_err, tx, buf)) => {
state.transfer = TransferPhase::Error(hub_err, tx, buf);
HAS_ERROR.store(true, Ordering::Release);
signal_swap_done(cs);
}
}
});
}
#[cfg(feature = "circular-dma")]
#[handler]
#[cfg_attr(feature = "iram", ram)]
pub(crate) fn hub75_frame_count_isr() {
critical_section::with(|cs| {
if let Some(clear) = CLEAR_INTERRUPT.borrow_ref(cs).as_ref() {
(clear)();
}
FRAME_COUNT.fetch_add(1, Ordering::Relaxed);
signal_swap_done(cs);
});
}
#[cfg(all(not(hub75_use_lcd_cam), not(feature = "circular-dma")))]
pub(crate) fn init_isr_state(tx: TxDriver, buf: BcmBuf) {
critical_section::with(|cs| {
*ISR_STATE.borrow_ref_mut(cs) = Some(IsrState {
transfer: TransferPhase::Idle(tx, buf),
current_fb_ptr: core::ptr::null(),
pending_planes: None,
pending_fb_ptr: core::ptr::null(),
});
});
}
#[cfg(all(hub75_use_lcd_cam, not(feature = "circular-dma")))]
pub(crate) fn init_isr_state(tx: TxDriver, buf: BcmBuf, word_size: WordSize) {
critical_section::with(|cs| {
*ISR_STATE.borrow_ref_mut(cs) = Some(IsrState {
transfer: TransferPhase::Idle(tx, buf),
word_size,
current_fb_ptr: core::ptr::null(),
pending_planes: None,
pending_fb_ptr: core::ptr::null(),
});
});
}
#[cfg(feature = "circular-dma")]
pub(crate) fn store_circular_state(
xfer: TxTransfer,
desc_ptr: *mut esp_hal::dma::DmaDescriptor,
desc_count: usize,
fb_ptr: *const (),
) {
critical_section::with(|cs| {
*CIRCULAR_STATE.borrow_ref_mut(cs) = Some(CircularState {
transfer: Some(xfer),
descriptors: desc_ptr,
desc_count,
current_fb_ptr: fb_ptr,
});
});
}
#[cfg(feature = "circular-dma")]
pub(crate) fn store_clear_interrupt(clear: fn()) {
critical_section::with(|cs| {
*CLEAR_INTERRUPT.borrow_ref_mut(cs) = Some(clear);
});
}
pub struct Hub75<DM: esp_hal::DriverMode, FB> {
_dm: core::marker::PhantomData<DM>,
_fb: core::marker::PhantomData<fn() -> FB>,
_not_sync: core::marker::PhantomData<*const ()>,
}
unsafe impl<DM: esp_hal::DriverMode, FB> Send for Hub75<DM, FB> {}
impl<DM: esp_hal::DriverMode, FB> Hub75<DM, FB> {
pub(crate) fn from_phantom() -> Self {
Self {
_dm: core::marker::PhantomData,
_fb: core::marker::PhantomData,
_not_sync: core::marker::PhantomData,
}
}
}
impl<DM: esp_hal::DriverMode, FB: FrameBuffer + 'static> Hub75<DM, FB> {
#[cfg(not(feature = "circular-dma"))]
pub fn restart(&self, fb: &'static FB) -> Result<(), Hub75Error> {
start_internal(fb)
}
pub fn frame_count(&self) -> u32 {
FRAME_COUNT.load(Ordering::Relaxed)
}
}
#[cfg(not(feature = "circular-dma"))]
pub(crate) fn start_internal(fb: &'static impl FrameBuffer) -> Result<(), Hub75Error> {
let planes = planes_from_fb(fb);
let plane_count = fb.plane_count();
critical_section::with(|cs| {
let mut borrow = ISR_STATE.borrow_ref_mut(cs);
let state = borrow.as_mut().expect("Hub75 not initialised");
let (tx, mut buf) =
match core::mem::replace(&mut state.transfer, TransferPhase::Transitioning) {
TransferPhase::Idle(tx, buf) | TransferPhase::Error(_, tx, buf) => (tx, buf),
other => {
state.transfer = other;
panic!("start() called while already running");
}
};
buf.reset_with_planes(planes, plane_count);
state.current_fb_ptr = fb as *const _ as *const ();
state.pending_planes = None;
state.pending_fb_ptr = core::ptr::null();
#[cfg(hub75_use_lcd_cam)]
let xfer_result = start_transfer(tx, buf, state.word_size);
#[cfg(not(hub75_use_lcd_cam))]
let xfer_result = start_transfer(tx, buf);
match xfer_result {
Ok(xfer) => {
state.transfer = TransferPhase::InFlight(xfer);
HAS_ERROR.store(false, Ordering::Release);
SWAP_DONE.store(false, Ordering::Relaxed);
Ok(())
}
Err((hub_err, tx, buf)) => {
state.transfer = TransferPhase::Error(hub_err, tx, buf);
HAS_ERROR.store(true, Ordering::Release);
Err(hub_err)
}
}
})
}
pub struct Hub75Swap<FB: 'static> {
old_fb_ptr: *mut FB,
#[cfg(not(feature = "circular-dma"))]
new_fb_ptr: *mut FB,
}
unsafe impl<FB: 'static> Send for Hub75Swap<FB> {}
impl<FB: FrameBuffer + 'static> Hub75Swap<FB> {
pub fn is_done(&self) -> bool {
#[cfg(not(feature = "circular-dma"))]
{
SWAP_DONE.load(Ordering::Acquire) || HAS_ERROR.load(Ordering::Acquire)
}
#[cfg(feature = "circular-dma")]
{
SWAP_DONE.load(Ordering::Acquire)
}
}
#[cfg(not(feature = "circular-dma"))]
pub fn wait(self) -> Result<&'static mut FB, (Hub75Error, &'static mut FB)> {
loop {
if HAS_ERROR.load(Ordering::Acquire) {
return critical_section::with(|cs| {
let mut borrow = ISR_STATE.borrow_ref_mut(cs);
let state = borrow.as_mut().unwrap();
state.pending_planes = None;
state.pending_fb_ptr = core::ptr::null();
let err = match &state.transfer {
TransferPhase::Error(err, _, _) => *err,
_ => 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 })
}
#[cfg(feature = "circular-dma")]
pub fn wait(self) -> Result<&'static mut FB, (Hub75Error, &'static mut FB)> {
while !SWAP_DONE.load(Ordering::Acquire) {
core::hint::spin_loop();
}
Ok(unsafe { &mut *self.old_fb_ptr })
}
#[cfg(not(feature = "circular-dma"))]
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(());
}
critical_section::with(|cs| {
if SWAP_DONE.load(Ordering::Relaxed) || HAS_ERROR.load(Ordering::Relaxed) {
return core::task::Poll::Ready(());
}
*SWAP_WAKER.borrow_ref_mut(cs) = Some(cx.waker().clone());
core::task::Poll::Pending
})
})
.await;
}
#[cfg(feature = "circular-dma")]
pub async fn wait_for_done(&mut self) {
core::future::poll_fn(|cx| {
if SWAP_DONE.load(Ordering::Acquire) {
return core::task::Poll::Ready(());
}
critical_section::with(|cs| {
if SWAP_DONE.load(Ordering::Relaxed) {
return core::task::Poll::Ready(());
}
*SWAP_WAKER.borrow_ref_mut(cs) = Some(cx.waker().clone());
core::task::Poll::Pending
})
})
.await;
}
}
#[cfg(not(feature = "circular-dma"))]
impl<DM: esp_hal::DriverMode, FB: FrameBuffer + 'static> Hub75<DM, FB> {
pub fn swap(&self, new_fb: &'static mut FB) -> Hub75Swap<FB> {
let new_planes = planes_from_fb(new_fb);
let new_fb_ptr = new_fb as *mut FB;
let old_fb_ptr = critical_section::with(|cs| {
let mut borrow = ISR_STATE.borrow_ref_mut(cs);
let state = borrow.as_mut().expect("Hub75 not initialised");
let old = state.current_fb_ptr;
state.pending_planes = Some(new_planes);
state.pending_fb_ptr = new_fb_ptr as *const ();
SWAP_DONE.store(false, Ordering::Relaxed);
old
});
Hub75Swap {
old_fb_ptr: old_fb_ptr as *mut FB,
new_fb_ptr,
}
}
}
#[cfg(feature = "circular-dma")]
impl<DM: esp_hal::DriverMode, FB: FrameBuffer + 'static> Hub75<DM, FB> {
pub fn swap(&self, new_fb: &'static mut FB) -> Hub75Swap<FB> {
let new_fb_ptr = new_fb as *mut FB;
let old_fb_ptr = critical_section::with(|cs| {
let mut borrow = CIRCULAR_STATE.borrow_ref_mut(cs);
let state = borrow.as_mut().expect("Hub75 not initialised");
let delta = new_fb_ptr as isize - state.current_fb_ptr as isize;
unsafe {
for i in 0..state.desc_count {
let desc = &mut *state.descriptors.add(i);
desc.buffer = desc.buffer.wrapping_byte_offset(delta);
}
}
let old_ptr = state.current_fb_ptr;
state.current_fb_ptr = new_fb_ptr as *const ();
SWAP_DONE.store(false, Ordering::Release);
old_ptr
});
Hub75Swap {
old_fb_ptr: old_fb_ptr as *mut FB,
}
}
}