use crate::cache::{clean_range, invalidate_range};
use crate::pac::VCMAILBOX;
const CHANNEL_PROPERTY_TAGS: u32 = 8;
const MAX_VALUE_WORDS: usize = 4;
const BUFFER_WORDS: usize = 2 + 3 + MAX_VALUE_WORDS + 1;
const RESPONSE_BIT: u32 = 1 << 31;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Error {
AddressMismatch,
RequestFailed,
TagNotAnswered,
ValueTooLarge,
NoDisplayMode,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum ClockId {
Emmc = 1,
Uart = 2,
Arm = 3,
Core = 4,
V3d = 5,
H264 = 6,
Isp = 7,
Sdram = 8,
Pixel = 9,
Pwm = 10,
#[cfg(feature = "bcm2711")]
Emmc2 = 12,
}
pub const THROTTLED_UNDER_VOLTAGE: u32 = 1 << 0;
pub const THROTTLED_ARM_FREQ_CAPPED: u32 = 1 << 1;
pub const THROTTLED_NOW: u32 = 1 << 2;
pub const THROTTLED_SOFT_TEMP_LIMIT: u32 = 1 << 3;
pub const THROTTLED_UNDER_VOLTAGE_EVER: u32 = 1 << 16;
pub const THROTTLED_ARM_FREQ_CAPPED_EVER: u32 = 1 << 17;
pub const THROTTLED_EVER: u32 = 1 << 18;
pub const THROTTLED_SOFT_TEMP_LIMIT_EVER: u32 = 1 << 19;
pub const EXPANDER_WL_ON: u32 = 129;
pub const EXPANDER_BT_ON: u32 = 128;
pub const EXPANDER_CAM_GPIO0: u32 = 133;
pub const EXPANDER_CAM_GPIO1: u32 = 134;
pub const POWER_DOMAIN_UNICAM1: u32 = 13;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum PowerDeviceId {
SdCard = 0,
Uart0 = 1,
Uart1 = 2,
UsbHcd = 3,
I2c0 = 4,
I2c1 = 5,
I2c2 = 6,
Spi = 7,
Ccp2tx = 8,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MemoryRegion {
pub base_address: u32,
pub size_bytes: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DisplaySize {
pub width: u32,
pub height: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Overscan {
pub top: u32,
pub bottom: u32,
pub left: u32,
pub right: u32,
}
impl Overscan {
pub fn is_zero(&self) -> bool {
self.top == 0 && self.bottom == 0 && self.left == 0 && self.right == 0
}
}
pub const EDID_BLOCK_LEN: usize = 128;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum PixelOrder {
Bgr = 0,
Rgb = 1,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DisplayId {
MainLcd,
AuxLcd,
Hdmi0,
Sdtv,
Hdmi1,
Other(u32),
}
impl DisplayId {
fn from_raw(raw: u32) -> Self {
match raw {
0 => Self::MainLcd,
1 => Self::AuxLcd,
2 => Self::Hdmi0,
3 => Self::Sdtv,
7 => Self::Hdmi1,
other => Self::Other(other),
}
}
}
pub const MAX_DISPLAYS: usize = 5;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Selection {
attached: [Option<DisplayId>; MAX_DISPLAYS],
count: usize,
chosen: Option<DisplayId>,
outcome: Outcome,
reported: Option<u32>,
}
impl Selection {
pub fn attached(&self) -> impl Iterator<Item = (u32, Option<DisplayId>)> + '_ {
self.attached[..self.count]
.iter()
.enumerate()
.map(|(number, id)| (number as u32, *id))
}
pub fn count(&self) -> usize {
self.count
}
pub fn chosen(&self) -> Option<DisplayId> {
self.chosen
}
pub fn outcome(&self) -> Outcome {
self.outcome
}
pub fn reported_count(&self) -> Option<u32> {
self.reported
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Outcome {
Sole,
Preferred,
Firmwares,
FellBack,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DisplayMode {
pub width: u32,
pub height: u32,
pub overscan: Overscan,
pub overscan_cleared: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Framebuffer {
pub address: u32,
pub size_bytes: u32,
pub pitch_bytes: u32,
pub width: u32,
pub height: u32,
pub virtual_height: u32,
pub depth_bits: u32,
}
impl Framebuffer {
pub fn pages(&self) -> u32 {
self.virtual_height / self.height
}
pub fn page_offset_bytes(&self, page: u32) -> u32 {
page * self.height * self.pitch_bytes
}
pub fn flush(&self) {
clean_range(self.address, self.size_bytes as usize);
}
pub fn flush_page(&self, page: u32) {
if page >= self.pages() {
return;
}
clean_range(
self.address + self.page_offset_bytes(page),
(self.height * self.pitch_bytes) as usize,
);
}
}
pub struct Mailbox {
vcmailbox: VCMAILBOX,
}
impl Mailbox {
pub fn new(vcmailbox: VCMAILBOX) -> Self {
Self { vcmailbox }
}
pub fn firmware_revision(&mut self) -> Result<u32, Error> {
let response = self.property_call(0x0000_0001, &[], 1)?;
Ok(response[0])
}
pub fn board_revision(&mut self) -> Result<u32, Error> {
let response = self.property_call(0x0001_0002, &[], 1)?;
Ok(response[0])
}
pub fn board_serial(&mut self) -> Result<u64, Error> {
let response = self.property_call(0x0001_0004, &[], 2)?;
Ok((response[0] as u64) | ((response[1] as u64) << 32))
}
pub fn mac_address(&mut self) -> Result<[u8; 6], Error> {
let response = self.property_call(0x0001_0003, &[], 2)?;
let low = response[0].to_le_bytes();
let high = response[1].to_le_bytes();
Ok([low[0], low[1], low[2], low[3], high[0], high[1]])
}
pub fn arm_memory(&mut self) -> Result<MemoryRegion, Error> {
self.memory_region(0x0001_0005)
}
pub fn vc_memory(&mut self) -> Result<MemoryRegion, Error> {
self.memory_region(0x0001_0006)
}
pub fn clock_rate_hz(&mut self, clock: ClockId) -> Result<u32, Error> {
let response = self.property_call(0x0003_0002, &[clock as u32], 2)?;
Ok(response[1])
}
pub fn max_clock_rate_hz(&mut self, clock: ClockId) -> Result<u32, Error> {
let response = self.property_call(0x0003_0004, &[clock as u32], 2)?;
Ok(response[1])
}
pub fn set_clock_rate_hz(&mut self, clock: ClockId, rate_hz: u32) -> Result<u32, Error> {
let response = self.property_call(0x0003_8002, &[clock as u32, rate_hz, 0], 3)?;
Ok(response[1])
}
pub fn set_clock_state(&mut self, clock: ClockId, on: bool) -> Result<bool, Error> {
let response = self.property_call(0x0003_8001, &[clock as u32, on as u32], 2)?;
Ok(response[1] & 0b10 == 0)
}
pub fn temperature_millicelsius(&mut self) -> Result<u32, Error> {
let response = self.property_call(0x0003_0006, &[0], 2)?;
Ok(response[1])
}
pub fn throttled(&mut self) -> Result<u32, Error> {
let response = self.property_call(0x0003_0046, &[0], 2)?;
Ok(response[1])
}
pub fn set_power_domain(&mut self, domain: u32, on: bool) -> Result<bool, Error> {
let response = self.property_call(0x0003_8030, &[domain, on as u32], 2)?;
Ok(response[1] != 0)
}
pub fn set_enable_qpu(&mut self, enable: bool) -> Result<(), Error> {
self.property_call(0x0003_0012, &[enable as u32], 1)?;
Ok(())
}
pub fn set_power_state(&mut self, device: PowerDeviceId, on: bool) -> Result<bool, Error> {
let state = if on { 0b11 } else { 0b10 };
let response = self.property_call(0x0002_8001, &[device as u32, state], 2)?;
Ok(response[1] & 0b10 == 0 && response[1] & 0b1 == if on { 1 } else { 0 })
}
pub fn set_expander_gpio(&mut self, pin: u32, on: bool) -> Result<(), Error> {
self.property_call(0x0003_8041, &[pin, on as u32], 2)?;
Ok(())
}
pub fn get_expander_gpio(&mut self, pin: u32) -> Result<bool, Error> {
let response = self.property_call(0x0003_0041, &[pin, 0], 2)?;
Ok(response[1] != 0)
}
pub fn display_size(&mut self) -> Result<DisplaySize, Error> {
let response = self.property_call(0x0004_0003, &[], 2)?;
Ok(DisplaySize {
width: response[0],
height: response[1],
})
}
pub fn overscan(&mut self) -> Result<Overscan, Error> {
let response = self.property_call(0x0004_000A, &[], 4)?;
Ok(Overscan {
top: response[0],
bottom: response[1],
left: response[2],
right: response[3],
})
}
pub fn set_overscan(&mut self, overscan: Overscan) -> Result<Overscan, Error> {
let response = self.property_call(
0x0004_800A,
&[overscan.top, overscan.bottom, overscan.left, overscan.right],
4,
)?;
Ok(Overscan {
top: response[0],
bottom: response[1],
left: response[2],
right: response[3],
})
}
pub fn edid_block(&mut self, block: u32) -> Result<Option<[u8; EDID_BLOCK_LEN]>, Error> {
const VALUE_WORDS: usize = 2 + EDID_BLOCK_LEN / 4;
const WORDS: usize = 2 + 3 + VALUE_WORDS + 1;
#[repr(C, align(16))]
struct Buffer {
words: [u32; WORDS],
}
let mut buffer = Buffer { words: [0; WORDS] };
buffer.words[0] = (WORDS * 4) as u32;
buffer.words[1] = 0;
buffer.words[2] = 0x0003_0020;
buffer.words[3] = (VALUE_WORDS * 4) as u32;
buffer.words[4] = 0;
buffer.words[5] = block;
let address = &buffer as *const Buffer as u32;
clean_range(address, WORDS * 4);
let echoed = self.call_raw(CHANNEL_PROPERTY_TAGS, address);
if echoed != address {
return Err(Error::AddressMismatch);
}
invalidate_range(address, WORDS * 4);
if buffer.words[1] & RESPONSE_BIT == 0 {
return Err(Error::RequestFailed);
}
if buffer.words[4] & RESPONSE_BIT == 0 {
return Err(Error::TagNotAnswered);
}
if buffer.words[6] != 0 {
return Ok(None);
}
let mut bytes = [0u8; EDID_BLOCK_LEN];
for (word, chunk) in buffer.words[7..7 + EDID_BLOCK_LEN / 4]
.iter()
.zip(bytes.as_chunks_mut::<4>().0)
{
*chunk = word.to_le_bytes();
}
Ok(Some(bytes))
}
pub fn num_displays(&mut self) -> Result<u32, Error> {
let response = self.property_call(0x0004_0013, &[], 1)?;
Ok(response[0])
}
pub fn set_display_num(&mut self, number: u32) -> Result<(), Error> {
self.property_call(0x0004_8013, &[number], 1)?;
Ok(())
}
pub fn display_id(&mut self, number: u32) -> Result<DisplayId, Error> {
let response = self.property_call(0x0004_0016, &[number], 1)?;
Ok(DisplayId::from_raw(response[0]))
}
pub fn select_display(&mut self, order: &[DisplayId]) -> Selection {
let reported = self.num_displays().ok();
let count = (reported.unwrap_or(0) as usize).min(MAX_DISPLAYS);
let mut attached = [None; MAX_DISPLAYS];
if count < 2 {
attached[0] = self.display_id(0).ok();
return Selection {
attached,
count: count.max(1),
chosen: attached[0],
outcome: Outcome::Sole,
reported,
};
}
for (number, id) in attached.iter_mut().enumerate().take(count) {
*id = self.display_id(number as u32).ok();
}
if order.is_empty() {
return Selection {
attached,
count,
chosen: attached[0],
outcome: Outcome::Firmwares,
reported,
};
}
for wanted in order {
let Some(number) = attached[..count].iter().position(|id| id == &Some(*wanted)) else {
continue;
};
if self.set_display_num(number as u32).is_ok() {
return Selection {
attached,
count,
chosen: Some(*wanted),
outcome: Outcome::Preferred,
reported,
};
}
}
let _ = self.set_display_num(0);
Selection {
attached,
count,
chosen: attached[0],
outcome: Outcome::FellBack,
reported,
}
}
pub fn display_mode(&mut self) -> Result<DisplayMode, Error> {
let overscan = self.overscan().unwrap_or(Overscan {
top: 0,
bottom: 0,
left: 0,
right: 0,
});
let inner = self.display_size()?;
if inner.width == 0 || inner.height == 0 {
return Err(Error::NoDisplayMode);
}
if overscan.is_zero() {
return Ok(DisplayMode {
width: inner.width,
height: inner.height,
overscan,
overscan_cleared: true,
});
}
let cleared = self
.set_overscan(Overscan {
top: 0,
bottom: 0,
left: 0,
right: 0,
})
.is_ok()
&& self.overscan().is_ok_and(|border| border.is_zero());
Ok(if cleared {
DisplayMode {
width: inner.width + overscan.left + overscan.right,
height: inner.height + overscan.top + overscan.bottom,
overscan,
overscan_cleared: true,
}
} else {
DisplayMode {
width: inner.width,
height: inner.height,
overscan,
overscan_cleared: false,
}
})
}
pub fn allocate_framebuffer(
&mut self,
width: u32,
height: u32,
depth_bits: u32,
pixel_order: PixelOrder,
) -> Result<Framebuffer, Error> {
self.allocate_framebuffer_paged(width, height, 1, depth_bits, pixel_order)
}
pub fn allocate_framebuffer_paged(
&mut self,
width: u32,
height: u32,
pages: u32,
depth_bits: u32,
pixel_order: PixelOrder,
) -> Result<Framebuffer, Error> {
const ALLOCATE_ALIGNMENT: u32 = 4096;
const WORDS: usize = 2 + (5 + 5 + 4 + 4 + 5 + 4) + 1;
#[repr(C, align(16))]
struct Buffer {
words: [u32; WORDS],
}
let virtual_height = height * pages.max(1);
let mut buffer = Buffer { words: [0; WORDS] };
buffer.words[0] = (WORDS * 4) as u32;
buffer.words[1] = 0;
buffer.words[2] = 0x0004_8003;
buffer.words[3] = 8;
buffer.words[4] = 0;
buffer.words[5] = width;
buffer.words[6] = height;
buffer.words[7] = 0x0004_8004;
buffer.words[8] = 8;
buffer.words[9] = 0;
buffer.words[10] = width;
buffer.words[11] = virtual_height;
buffer.words[12] = 0x0004_8005;
buffer.words[13] = 4;
buffer.words[14] = 0;
buffer.words[15] = depth_bits;
buffer.words[16] = 0x0004_8006;
buffer.words[17] = 4;
buffer.words[18] = 0;
buffer.words[19] = pixel_order as u32;
buffer.words[20] = 0x0004_0001;
buffer.words[21] = 8;
buffer.words[22] = 0;
buffer.words[23] = ALLOCATE_ALIGNMENT;
buffer.words[24] = 0;
buffer.words[25] = 0x0004_0008;
buffer.words[26] = 4;
buffer.words[27] = 0;
buffer.words[28] = 0;
let address = &buffer as *const Buffer as u32;
clean_range(address, WORDS * 4);
let echoed = self.call_raw(CHANNEL_PROPERTY_TAGS, address);
if echoed != address {
return Err(Error::AddressMismatch);
}
invalidate_range(address, WORDS * 4);
if buffer.words[1] & RESPONSE_BIT == 0 {
return Err(Error::RequestFailed);
}
for &code_index in &[4, 9, 14, 18, 22, 27] {
if buffer.words[code_index] & RESPONSE_BIT == 0 {
return Err(Error::TagNotAnswered);
}
}
Ok(Framebuffer {
address: from_vc_bus_address(buffer.words[23]),
size_bytes: buffer.words[24],
pitch_bytes: buffer.words[28],
width: buffer.words[5],
height: buffer.words[6],
virtual_height: buffer.words[11],
depth_bits: buffer.words[15],
})
}
pub fn set_virtual_offset(&mut self, x: u32, y: u32) -> Result<(), Error> {
self.property_call(0x0004_8009, &[x, y], 2)?;
Ok(())
}
pub fn wait_for_vsync(&mut self) -> Result<(), Error> {
self.property_call(0x0004_800e, &[0], 1)?;
Ok(())
}
pub fn vchiq_init(&mut self, slot_bus_address: u32) -> Result<u32, Error> {
let response = self.property_call(0x0004_8010, &[slot_bus_address], 1)?;
Ok(response[0])
}
pub fn set_touch_buffer_address(&mut self, address: u32) -> Result<(), Error> {
self.property_call(0x0004_801f, &[to_vc_bus_address(address)], 1)?;
Ok(())
}
fn memory_region(&mut self, tag: u32) -> Result<MemoryRegion, Error> {
let response = self.property_call(tag, &[], 2)?;
Ok(MemoryRegion {
base_address: response[0],
size_bytes: response[1],
})
}
fn property_call(
&mut self,
tag: u32,
request: &[u32],
response_words: usize,
) -> Result<[u32; MAX_VALUE_WORDS], Error> {
if request.len() > MAX_VALUE_WORDS || response_words > MAX_VALUE_WORDS {
return Err(Error::ValueTooLarge);
}
#[repr(C, align(16))]
struct Buffer {
words: [u32; BUFFER_WORDS],
}
let mut buffer = Buffer {
words: [0; BUFFER_WORDS],
};
buffer.words[0] = (BUFFER_WORDS * 4) as u32;
buffer.words[1] = 0;
buffer.words[2] = tag;
buffer.words[3] = (MAX_VALUE_WORDS * 4) as u32;
buffer.words[4] = 0;
buffer.words[5..5 + request.len()].copy_from_slice(request);
let address = &buffer as *const Buffer as u32;
clean_range(address, BUFFER_WORDS * 4);
let echoed = self.call_raw(CHANNEL_PROPERTY_TAGS, address);
if echoed != address {
return Err(Error::AddressMismatch);
}
invalidate_range(address, BUFFER_WORDS * 4);
if buffer.words[1] & RESPONSE_BIT == 0 {
return Err(Error::RequestFailed);
}
if buffer.words[4] & RESPONSE_BIT == 0 {
return Err(Error::TagNotAnswered);
}
let mut response = [0u32; MAX_VALUE_WORDS];
response[..response_words].copy_from_slice(&buffer.words[5..5 + response_words]);
Ok(response)
}
fn call_raw(&mut self, channel: u32, buffer_address: u32) -> u32 {
let bus_address = to_vc_bus_address(buffer_address);
let message = (bus_address & !0xF) | (channel & 0xF);
while self.vcmailbox.status1().read().bits() & (1 << 31) != 0 {}
unsafe {
self.vcmailbox.write().write_with_zero(|w| w.bits(message));
}
loop {
while self.vcmailbox.status0().read().empty().bit_is_set() {}
let response = self.vcmailbox.read().read().bits();
if response & 0xF == channel {
return (response & !0xF) - 0xC000_0000;
}
}
}
}
fn to_vc_bus_address(physical_address: u32) -> u32 {
(physical_address & 0x3FFF_FFFF) | 0xC000_0000
}
fn from_vc_bus_address(bus_address: u32) -> u32 {
bus_address & 0x3FFF_FFFF
}