use core::ptr::null;
use core::ptr::null_mut;
use esp_hal::dma::DmaDescriptor;
use esp_hal::dma::DmaTxBuffer;
use esp_hal::dma::Preparation;
#[cfg(feature = "iram")]
use esp_hal::ram;
use static_cell::StaticCell;
use crate::framebuffer::BcmSegment;
use crate::framebuffer::FrameBuffer;
pub(crate) const MAX_SEGMENTS: usize = 320;
const MAX_GROUPS_PER_PERIOD: usize = crate::framebuffer::BCM_SEQUENCE_CAPACITY;
const EMPTY_SEGMENT: BcmSegment = BcmSegment {
ptr: null(),
len: 0,
reps: 0,
};
static SEGMENT_CACHE: StaticCell<SegmentCache> = StaticCell::new();
fn segment_cache() -> &'static mut SegmentCache {
SEGMENT_CACHE.init_with(SegmentCache::new)
}
pub(crate) struct SegmentCache {
pub(crate) segments: [BcmSegment; MAX_SEGMENTS],
pub(crate) count: usize,
pub(crate) segments_per_group: usize,
pub(crate) group_descs: [usize; MAX_GROUPS_PER_PERIOD],
pub(crate) groups_per_period: usize,
}
impl SegmentCache {
pub const fn new() -> Self {
Self {
segments: [EMPTY_SEGMENT; MAX_SEGMENTS],
count: 0,
segments_per_group: 1,
group_descs: [0; MAX_GROUPS_PER_PERIOD],
groups_per_period: 1,
}
}
pub(crate) fn fill<FB: FrameBuffer>(&mut self, fb: &FB) {
const {
assert!(
FB::BCM_SEGMENT_COUNT <= MAX_SEGMENTS,
"framebuffer BCM segment count exceeds MAX_SEGMENTS"
);
assert!(
FB::BCM_SEQUENCE_LEN % FB::BCM_SEGMENTS_PER_GROUP == 0,
"BCM_SEQUENCE_LEN must be divisible by BCM_SEGMENTS_PER_GROUP"
);
}
let count = fb.bcm_segment_count();
let spg = fb.bcm_segments_per_group();
assert!(
count <= 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}"
);
self.count = count;
self.segments_per_group = spg;
for i in 0..count {
let segment = fb.bcm_segment(i);
debug_assert!(
!segment.ptr.is_null(),
"segment {i} returned a null pointer"
);
self.segments[i] = segment;
}
let groups_per_period = FB::BCM_SEQUENCE_LEN / FB::BCM_SEGMENTS_PER_GROUP;
assert!(
groups_per_period <= MAX_GROUPS_PER_PERIOD,
"groups per period {groups_per_period} exceeds MAX_GROUPS_PER_PERIOD"
);
self.groups_per_period = groups_per_period;
let mut group = 0;
while group < groups_per_period {
self.group_descs[group] = crate::group_descriptor_count::<FB>(
group,
FB::BCM_SEGMENTS_PER_GROUP,
crate::MAX_DMA_CHUNK_SIZE,
);
group += 1;
}
}
#[cfg_attr(feature = "iram", ram)]
pub fn group_count(&self) -> usize {
self.count / self.segments_per_group
}
#[cfg_attr(feature = "iram", ram)]
pub fn group_descriptor_count(&self, group_idx: usize) -> usize {
self.group_descs[group_idx % self.groups_per_period]
}
#[cfg(esp32c6)]
#[cfg_attr(feature = "iram", ram)]
pub fn group_byte_count(&self, group_idx: usize) -> usize {
let start = group_idx * self.segments_per_group;
let end = start + self.segments_per_group;
let mut total = 0;
let mut i = start;
while i < end {
total += self.segments[i].len * self.segments[i].reps;
i += 1;
}
total
}
}
pub(crate) struct BcmBuf {
descriptors: &'static mut [DmaDescriptor],
cache: &'static mut SegmentCache,
current_group: usize,
}
impl BcmBuf {
pub(crate) fn new(descriptors: &'static mut [DmaDescriptor]) -> Self {
Self {
descriptors,
cache: segment_cache(),
current_group: 0,
}
}
pub(crate) fn bind_cache<FB: FrameBuffer>(&mut self, fb: &FB) {
self.cache.fill(fb);
debug_assert!(
self.descriptors.len() >= crate::dma_descriptor_count::<FB>(crate::MAX_DMA_CHUNK_SIZE),
"not enough DMA descriptors: have {}, need {}",
self.descriptors.len(),
crate::dma_descriptor_count::<FB>(crate::MAX_DMA_CHUNK_SIZE),
);
self.current_group = 0;
}
#[cfg_attr(feature = "iram", ram)]
pub(crate) fn advance(&mut self) -> bool {
let group_count = self.cache.group_count();
self.current_group += 1;
if self.current_group >= group_count {
self.current_group = 0;
return true;
}
false
}
#[cfg_attr(feature = "iram", ram)]
pub(crate) fn apply_delta(&mut self, delta: isize) {
let cache = &mut *self.cache;
for i in 0..cache.count {
cache.segments[i].ptr = cache.segments[i].ptr.wrapping_byte_offset(delta);
}
}
#[cfg(esp32c6)]
#[cfg_attr(feature = "iram", ram)]
pub(crate) fn current_transfer_len(&self) -> usize {
self.cache.group_byte_count(self.current_group)
}
}
unsafe impl Send for BcmBuf {}
unsafe impl DmaTxBuffer for BcmBuf {
type View = Self;
type Final = Self;
#[cfg_attr(feature = "iram", ram)]
fn prepare(&mut self) -> Preparation {
self.prepare_descriptors()
}
#[cfg_attr(feature = "iram", ram)]
fn into_view(self) -> Self::View {
self
}
#[cfg_attr(feature = "iram", ram)]
fn from_view(view: Self::View) -> Self::Final {
view
}
}
impl BcmBuf {
#[cfg_attr(feature = "iram", ram)]
fn prepare_descriptors(&mut self) -> Preparation {
let cache = &*self.cache;
let spg = cache.segments_per_group;
let start = self.current_group * spg;
let total_descs = cache.group_descriptor_count(self.current_group);
super::fill_descriptor_chain(
&mut self.descriptors[..total_descs],
spg,
|i| cache.segments[start + i],
total_descs,
null_mut(),
true,
);
super::make_preparation(self.descriptors)
}
}