S549 · SOURCE-BOUND GATE EVIDENCE
S549 · R1 ekran: RP1 DSI host register haritası ve D-PHY zamanlama modeli
tam S549 implementation modülü → Operations --test hedefi ile bağlı tam focused test → ayrı Operations kaydı Bu sayfa yalnız S549 kapısına aittir; komşu kapıların kaynakları bu kabulün içine katılmaz.
S549Focused kod testiOperations id exactsource SHA exacttest target exact
operation: g8l-s549-r1-rp1-dsi-host-register-map-dphy-timing-model
uygulama/model · focused test · Operations · 3 exact excerpt
sequence-bound=true · implementation-bound=true
01 · Yürütme / doğrulama kodu
Kapının gerçek repository sözleşmesi
tam dosyaL1–L871
kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s549_r1_rp1_dsi_host_register_map_dphy_timing_model.rs::S549 r1 rp1 dsi host register map dphy timing model implementation
#![allow(unexpected_cfgs)]
//! S549 models the RP1 (Raspberry Pi 5 southbridge) MIPI DSI host bring-up as
//! data: a symbolic register map, a checked D-PHY timing calculator, and a
//! bounded register program encoder for two R1 panel profiles.
//!
//! What this gate does NOT claim: no panel, DSI host, D-PHY, board, UART, SD
//! card, power transition or runtime observation exists for S549. The
//! register offsets, widths, reset values and access classes are model
//! placeholders that must be bound to the RP1 datasheet later; they are NOT
//! hardware-validated. Nothing here is wired into a boot, IRQ, scheduler or
//! driver path. The module performs no device operation, emits no UART
//! text, does not rerun S540 or S543, and cannot promote either physical RED.
//!
//! Predecessor: S548 (R1 stage 2). Next gate: S550.
use alloc::vec::Vec;
pub const S549_SEQUENCE: usize = 549;
pub const S549_EXPECTED_PREDECESSOR: usize = 548;
pub const S549_R1_STAGE: u8 = 2;
pub const S549_R1_RANGE_FIRST: usize = 536;
pub const S549_R1_RANGE_LAST: usize = 568;
pub const S549_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0;
pub const S549_PHYSICAL_OBSERVATIONS: usize = 0;
pub const S549_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0;
pub const S549_SD_WRITES: usize = 0;
pub const S549_UART_OPENS: usize = 0;
pub const S549_POWER_TRANSITIONS: usize = 0;
pub const S549_NEW_IMMUTABLE_RAW_CAPTURES: usize = 0;
pub const S549_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S549_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S549_AUTOMATIC_PROMOTION: bool = false;
pub const S549_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false;
pub const S549_HARDWARE_PRESENT: bool = false;
pub const S549_R1_ACCEPTANCE_COMPLETE: bool = false;
pub const RUNBOOK_EXECUTED_IN_S549: bool = false;
/// Register offsets below are symbolic model placeholders (not RP1-validated).
pub const S549_REGISTER_OFFSETS_HARDWARE_VALIDATED: bool = false;
pub const S549_REGISTER_WIDTH_BITS: u8 = 32;
pub const S549_REGISTER_COUNT: usize = 17;
pub const S549_DPHY_SPEC_RANGE_COUNT: usize = 10;
pub const S549_SUPPORTED_LANE_COUNTS: [u8; 3] = [1, 2, 4];
pub const S549_SUPPORTED_BITS_PER_PIXEL: [u8; 3] = [16, 18, 24];
pub const S549_UI_PER_BYTE_CLOCK: u64 = 8;
pub const S549_MIN_HS_BIT_RATE_BPS: u64 = 80_000_000;
pub const S549_MAX_HS_BIT_RATE_BPS: u64 = 1_500_000_000;
pub const S549_MAX_ACTIVE_PIXELS: u32 = 4096;
pub const S549_MAX_BLANKING_PIXELS: u32 = 1023;
pub const S549_MAX_PROGRAM_WRITES: usize = 16;
pub const S549_PROGRAM_WRITE_COUNT: usize = 13;
pub const S549_PICOSECONDS_PER_SECOND: u64 = 1_000_000_000_000;
pub const S549_NANOSECONDS_PER_SECOND: u64 = 1_000_000_000;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS549RegisterAccess {
ReadOnly,
ReadWrite,
WriteOnly,
Write1Clear,
}
impl G8lS549RegisterAccess {
pub const fn is_writable(self) -> bool {
!matches!(self, Self::ReadOnly)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS549DsiRegister {
pub name: &'static str,
pub offset: u32,
pub width_bits: u8,
pub reset_value: u32,
pub access: G8lS549RegisterAccess,
}
const fn reg(
name: &'static str,
offset: u32,
reset_value: u32,
access: G8lS549RegisterAccess,
) -> G8lS549DsiRegister {
G8lS549DsiRegister {
name,
offset,
width_bits: S549_REGISTER_WIDTH_BITS,
reset_value,
access,
}
}
/// Symbolic RP1 DSI host register map. Offsets are placeholders to be bound
/// to the RP1 datasheet later; they are NOT hardware-validated.
pub const S549_DSI_REGISTER_MAP: [G8lS549DsiRegister; S549_REGISTER_COUNT] = [
reg("DSI_CTRL", 0x000, 0, G8lS549RegisterAccess::ReadWrite),
reg("DSI_STATUS", 0x004, 0, G8lS549RegisterAccess::ReadOnly),
reg(
"DSI_INT_STATUS",
0x008,
0,
G8lS549RegisterAccess::Write1Clear,
),
reg("DSI_INT_ENABLE", 0x00C, 0, G8lS549RegisterAccess::ReadWrite),
reg("DSI_HS_CLK", 0x010, 0, G8lS549RegisterAccess::ReadWrite),
reg("DSI_LANE_CFG", 0x014, 0, G8lS549RegisterAccess::ReadWrite),
reg(
"DSI_PHY_TIMING_HS",
0x018,
0,
G8lS549RegisterAccess::ReadWrite,
),
reg(
"DSI_PHY_TIMING_CLK",
0x01C,
0,
G8lS549RegisterAccess::ReadWrite,
),
reg(
"DSI_PHY_TIMING_CLK2",
0x020,
0,
G8lS549RegisterAccess::ReadWrite,
),
reg("DSI_PKT_HDR", 0x024, 0, G8lS549RegisterAccess::WriteOnly),
reg(
"DSI_PKT_PAYLOAD",
0x028,
0,
G8lS549RegisterAccess::WriteOnly,
),
reg(
"DSI_VID_TIMING_HACTIVE",
0x030,
0,
G8lS549RegisterAccess::ReadWrite,
),
reg(
"DSI_VID_TIMING_HBLANK",
0x034,
0,
G8lS549RegisterAccess::ReadWrite,
),
reg(
"DSI_VID_TIMING_VACTIVE",
0x038,
0,
G8lS549RegisterAccess::ReadWrite,
),
reg(
"DSI_VID_TIMING_VBLANK",
0x03C,
0,
G8lS549RegisterAccess::ReadWrite,
),
reg(
"DSI_VID_MODE_CFG",
0x040,
0,
G8lS549RegisterAccess::ReadWrite,
),
reg(
"DSI_ID",
0x0FC,
0x4453_4931,
G8lS549RegisterAccess::ReadOnly,
),
];
pub const S549_CTRL_ENABLE: u32 = 1 << 0;
pub const S549_CTRL_VIDEO_MODE: u32 = 1 << 1;
pub const S549_INT_ENABLE_PANEL_ERROR: u32 = 1 << 0;
pub const S549_VID_MODE_CFG_BURST: u32 = 1 << 0;
/// One MIPI D-PHY global-operation timing range. Minimum = `min_ns` +
/// `min_ui` unit intervals; maximum (when the spec bounds it) = `max_ns` +
/// `max_ui`. Composite rows bound the sum of two parameters.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS549DphySpecRange {
pub name: &'static str,
pub min_ns: u64,
pub min_ui: u64,
pub max: Option<(u64, u64)>,
}
const fn range(
name: &'static str,
min_ns: u64,
min_ui: u64,
max: Option<(u64, u64)>,
) -> G8lS549DphySpecRange {
G8lS549DphySpecRange {
name,
min_ns,
min_ui,
max,
}
}
pub const S549_DPHY_SPEC_RANGES: [G8lS549DphySpecRange; S549_DPHY_SPEC_RANGE_COUNT] = [
range("T_LPX", 50, 0, None),
range("T_HS_PREPARE", 40, 4, Some((85, 6))),
range("T_HS_PREPARE_PLUS_ZERO", 145, 10, None),
range("T_HS_TRAIL", 60, 4, None),
range("T_HS_EXIT", 100, 0, None),
range("T_CLK_PREPARE", 38, 0, Some((95, 0))),
range("T_CLK_PREPARE_PLUS_ZERO", 300, 0, None),
range("T_CLK_PRE", 0, 8, None),
range("T_CLK_POST", 60, 52, None),
range("T_CLK_TRAIL", 60, 0, None),
];
const SPEC_LPX: usize = 0;
const SPEC_HS_PREPARE: usize = 1;
const SPEC_HS_PREPARE_PLUS_ZERO: usize = 2;
const SPEC_HS_TRAIL: usize = 3;
const SPEC_HS_EXIT: usize = 4;
const SPEC_CLK_PREPARE: usize = 5;
const SPEC_CLK_PREPARE_PLUS_ZERO: usize = 6;
const SPEC_CLK_PRE: usize = 7;
const SPEC_CLK_POST: usize = 8;
const SPEC_CLK_TRAIL: usize = 9;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS549PanelProfile {
Phone720x1280Lanes2,
Tablet1200x1920Lanes4,
}
impl G8lS549PanelProfile {
pub const fn id(self) -> u8 {
match self {
Self::Phone720x1280Lanes2 => 1,
Self::Tablet1200x1920Lanes4 => 2,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS549PanelMode {
pub hactive: u32,
pub hfp: u32,
pub hbp: u32,
pub hsync: u32,
pub vactive: u32,
pub vfp: u32,
pub vbp: u32,
pub vsync: u32,
pub refresh_hz: u64,
pub lanes: u8,
pub bits_per_pixel: u8,
}
pub const fn canonical_s549_panel_mode(profile: G8lS549PanelProfile) -> G8lS549PanelMode {
match profile {
G8lS549PanelProfile::Phone720x1280Lanes2 => G8lS549PanelMode {
hactive: 720,
hfp: 40,
hbp: 40,
hsync: 10,
vactive: 1280,
vfp: 10,
vbp: 10,
vsync: 4,
refresh_hz: 60,
lanes: 2,
bits_per_pixel: 24,
},
G8lS549PanelProfile::Tablet1200x1920Lanes4 => G8lS549PanelMode {
hactive: 1200,
hfp: 60,
hbp: 60,
hsync: 20,
vactive: 1920,
vfp: 20,
vbp: 20,
vsync: 4,
refresh_hz: 60,
lanes: 4,
bits_per_pixel: 24,
},
}
}
/// D-PHY timings in unit intervals (UI = one HS bit period).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS549DphyTimingUi {
pub t_lpx: u32,
pub t_hs_prepare: u32,
pub t_hs_zero: u32,
pub t_hs_trail: u32,
pub t_hs_exit: u32,
pub t_clk_prepare: u32,
pub t_clk_zero: u32,
pub t_clk_pre: u32,
pub t_clk_post: u32,
pub t_clk_trail: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS549DsiHostReceipt {
pub sequence: usize,
pub predecessor_sequence: usize,
pub r1_stage: u8,
pub profile_id: u8,
pub mode: G8lS549PanelMode,
pub htotal: u32,
pub vtotal: u32,
pub pixel_clock_hz: u64,
pub hs_bit_rate_bps: u64,
pub byte_clock_hz: u64,
pub ui_ps: u64,
pub timing: G8lS549DphyTimingUi,
pub program_write_count: usize,
pub program_checksum: u64,
pub register_offsets_hardware_validated: bool,
pub s540_physical_verdict_retained_red: bool,
pub s543_physical_verdict_retained_red: bool,
pub automatic_promotion: bool,
pub hardware_present: bool,
pub supported_profile_runtime_observations: usize,
pub physical_observations: usize,
pub runbook_executed: bool,
}
#[derive(Debug)]
pub struct G8lS549DsiHostState {
receipt: Option<G8lS549DsiHostReceipt>,
}
impl G8lS549DsiHostState {
pub const fn new() -> Self {
Self { receipt: None }
}
pub const fn receipt(&self) -> Option<G8lS549DsiHostReceipt> {
self.receipt
}
}
impl Default for G8lS549DsiHostState {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS549DsiHostOutcome {
Published(G8lS549DsiHostReceipt),
Retained(G8lS549DsiHostReceipt),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS549DsiHostError {
UnsupportedLaneCount,
UnsupportedBitsPerPixel,
ZeroDimension,
ActiveOutOfRange,
BlankingOutOfRange,
ArithmeticOverflow,
HsBitRateNotLaneDivisible,
HsBitRateBelowMinimum,
HsBitRateAboveMaximum,
TimingBelowSpecMinimum,
TimingAboveSpecMaximum,
TimingNotByteClockAligned,
RegisterMapMalformed,
RegisterUnknown,
RegisterNotWritable,
RegisterValueExceedsWidth,
RegisterFieldOverflow,
ProgramBufferOverflow,
PublishedStateDrift,
}
impl G8lS549DsiHostError {
pub const fn diagnostic_code(self) -> u64 {
match self {
Self::UnsupportedLaneCount => 1,
Self::UnsupportedBitsPerPixel => 2,
Self::ZeroDimension => 3,
Self::ActiveOutOfRange => 4,
Self::BlankingOutOfRange => 5,
Self::ArithmeticOverflow => 6,
Self::HsBitRateNotLaneDivisible => 7,
Self::HsBitRateBelowMinimum => 8,
Self::HsBitRateAboveMaximum => 9,
Self::TimingBelowSpecMinimum => 10,
Self::TimingAboveSpecMaximum => 11,
Self::TimingNotByteClockAligned => 12,
Self::RegisterMapMalformed => 13,
Self::RegisterUnknown => 14,
Self::RegisterNotWritable => 15,
Self::RegisterValueExceedsWidth => 16,
Self::RegisterFieldOverflow => 17,
Self::ProgramBufferOverflow => 18,
Self::PublishedStateDrift => 19,
}
}
}
type S549Result<T> = Result<T, G8lS549DsiHostError>;
pub fn validate_s549_register_map(map: &[G8lS549DsiRegister]) -> S549Result<()> {
if map.is_empty() {
return Err(G8lS549DsiHostError::RegisterMapMalformed);
}
for (index, register) in map.iter().enumerate() {
if register.name.is_empty()
|| register.width_bits != S549_REGISTER_WIDTH_BITS
|| register.offset % 4 != 0
{
return Err(G8lS549DsiHostError::RegisterMapMalformed);
}
if index > 0 {
let previous = map[index - 1];
// Strictly increasing offsets with no overlap of the previous word.
if register.offset < previous.offset.saturating_add(4) {
return Err(G8lS549DsiHostError::RegisterMapMalformed);
}
}
if map[..index].iter().any(|other| other.name == register.name) {
return Err(G8lS549DsiHostError::RegisterMapMalformed);
}
}
Ok(())
}
pub fn lookup_s549_register(
map: &[G8lS549DsiRegister],
name: &str,
) -> S549Result<G8lS549DsiRegister> {
map.iter()
.copied()
.find(|register| register.name == name)
.ok_or(G8lS549DsiHostError::RegisterUnknown)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS549RegisterWrite {
pub offset: u32,
pub value: u32,
}
/// Bounded register program (at most `S549_MAX_PROGRAM_WRITES` writes).
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct G8lS549RegisterProgram {
writes: Vec<G8lS549RegisterWrite>,
}
impl G8lS549RegisterProgram {
pub fn new() -> Self {
Self { writes: Vec::new() }
}
pub fn writes(&self) -> &[G8lS549RegisterWrite] {
&self.writes
}
pub fn push(
&mut self,
map: &[G8lS549DsiRegister],
name: &str,
value: u32,
) -> S549Result<G8lS549RegisterWrite> {
let register = lookup_s549_register(map, name)?;
if !register.access.is_writable() {
return Err(G8lS549DsiHostError::RegisterNotWritable);
}
if register.width_bits < 32 && value >> register.width_bits != 0 {
return Err(G8lS549DsiHostError::RegisterValueExceedsWidth);
}
if self.writes.len() >= S549_MAX_PROGRAM_WRITES {
return Err(G8lS549DsiHostError::ProgramBufferOverflow);
}
let write = G8lS549RegisterWrite {
offset: register.offset,
value,
};
self.writes.push(write);
Ok(write)
}
/// FNV-1a over (offset, value) words; wrapping arithmetic is intentional
/// for the digest and never feeds a timing computation.
pub fn checksum(&self) -> u64 {
let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
for write in &self.writes {
for word in [write.offset, write.value] {
for byte in word.to_le_bytes() {
hash ^= u64::from(byte);
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
}
}
}
hash
}
}
fn checked_mul(a: u64, b: u64) -> S549Result<u64> {
a.checked_mul(b)
.ok_or(G8lS549DsiHostError::ArithmeticOverflow)
}
fn checked_add(a: u64, b: u64) -> S549Result<u64> {
a.checked_add(b)
.ok_or(G8lS549DsiHostError::ArithmeticOverflow)
}
fn ceil_div(numerator: u64, denominator: u64) -> S549Result<u64> {
if denominator == 0 {
return Err(G8lS549DsiHostError::ArithmeticOverflow);
}
checked_add(numerator, denominator - 1).map(|sum| sum / denominator)
}
pub fn validate_s549_panel_mode(mode: G8lS549PanelMode) -> S549Result<()> {
if !S549_SUPPORTED_LANE_COUNTS.contains(&mode.lanes) {
return Err(G8lS549DsiHostError::UnsupportedLaneCount);
}
if !S549_SUPPORTED_BITS_PER_PIXEL.contains(&mode.bits_per_pixel) {
return Err(G8lS549DsiHostError::UnsupportedBitsPerPixel);
}
if mode.hactive == 0 || mode.vactive == 0 || mode.refresh_hz == 0 {
return Err(G8lS549DsiHostError::ZeroDimension);
}
if mode.hactive > S549_MAX_ACTIVE_PIXELS || mode.vactive > S549_MAX_ACTIVE_PIXELS {
return Err(G8lS549DsiHostError::ActiveOutOfRange);
}
for blanking in [
mode.hfp, mode.hbp, mode.hsync, mode.vfp, mode.vbp, mode.vsync,
] {
if blanking == 0 || blanking > S549_MAX_BLANKING_PIXELS {
return Err(G8lS549DsiHostError::BlankingOutOfRange);
}
}
Ok(())
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS549LinkClocks {
pub htotal: u32,
pub vtotal: u32,
pub pixel_clock_hz: u64,
pub hs_bit_rate_bps: u64,
pub byte_clock_hz: u64,
pub ui_ps: u64,
}
/// pixel clock = htotal * vtotal * refresh; HS bit rate per lane =
/// pixel clock * bpp / lanes; byte clock = HS bit rate / 8. All checked.
pub fn compute_s549_link_clocks(mode: G8lS549PanelMode) -> S549Result<G8lS549LinkClocks> {
validate_s549_panel_mode(mode)?;
let htotal = mode.hactive + mode.hfp + mode.hbp + mode.hsync;
let vtotal = mode.vactive + mode.vfp + mode.vbp + mode.vsync;
let pixel_clock_hz = checked_mul(
checked_mul(u64::from(htotal), u64::from(vtotal))?,
mode.refresh_hz,
)?;
let link_bits_per_second = checked_mul(pixel_clock_hz, u64::from(mode.bits_per_pixel))?;
if link_bits_per_second % u64::from(mode.lanes) != 0 {
return Err(G8lS549DsiHostError::HsBitRateNotLaneDivisible);
}
let hs_bit_rate_bps = link_bits_per_second / u64::from(mode.lanes);
if hs_bit_rate_bps < S549_MIN_HS_BIT_RATE_BPS {
return Err(G8lS549DsiHostError::HsBitRateBelowMinimum);
}
if hs_bit_rate_bps > S549_MAX_HS_BIT_RATE_BPS {
return Err(G8lS549DsiHostError::HsBitRateAboveMaximum);
}
Ok(G8lS549LinkClocks {
htotal,
vtotal,
pixel_clock_hz,
hs_bit_rate_bps,
byte_clock_hz: hs_bit_rate_bps / S549_UI_PER_BYTE_CLOCK,
ui_ps: S549_PICOSECONDS_PER_SECOND / hs_bit_rate_bps,
})
}
/// Minimum UI count for a spec row: ceil(min_ns * bit_rate / 1e9) + min_ui.
pub fn s549_spec_min_ui(range: G8lS549DphySpecRange, hs_bit_rate_bps: u64) -> S549Result<u64> {
let ns_part = ceil_div(
checked_mul(range.min_ns, hs_bit_rate_bps)?,
S549_NANOSECONDS_PER_SECOND,
)?;
checked_add(ns_part, range.min_ui)
}
/// Maximum UI count for a bounded row: floor(max_ns * bit_rate / 1e9) + max_ui.
pub fn s549_spec_max_ui(
range: G8lS549DphySpecRange,
hs_bit_rate_bps: u64,
) -> S549Result<Option<u64>> {
match range.max {
None => Ok(None),
Some((max_ns, max_ui)) => {
let ns_part = checked_mul(max_ns, hs_bit_rate_bps)? / S549_NANOSECONDS_PER_SECOND;
checked_add(ns_part, max_ui).map(Some)
}
}
}
fn round_up_to_byte_clock(ui: u64) -> S549Result<u64> {
ceil_div(ui, S549_UI_PER_BYTE_CLOCK)
.and_then(|bytes| checked_mul(bytes, S549_UI_PER_BYTE_CLOCK))
}
fn to_u32(value: u64) -> S549Result<u32> {
u32::try_from(value).map_err(|_| G8lS549DsiHostError::ArithmeticOverflow)
}
/// Picks the spec minimum rounded up to byte-clock granularity (model
/// policy: the host programs D-PHY timings in byte clocks). A rounded value
/// above the spec maximum fails closed instead of being silently clamped.
fn derive_single(index: usize, hs_bit_rate_bps: u64, already_ui: u64) -> S549Result<u32> {
let spec = S549_DPHY_SPEC_RANGES[index];
let min = s549_spec_min_ui(spec, hs_bit_rate_bps)?;
let remaining = min.saturating_sub(already_ui);
let chosen = round_up_to_byte_clock(remaining)?;
if let Some(max) = s549_spec_max_ui(spec, hs_bit_rate_bps)? {
if checked_add(chosen, already_ui)? > max {
return Err(G8lS549DsiHostError::TimingAboveSpecMaximum);
}
}
to_u32(chosen)
}
pub fn derive_s549_dphy_timing(hs_bit_rate_bps: u64) -> S549Result<G8lS549DphyTimingUi> {
let t_lpx = derive_single(SPEC_LPX, hs_bit_rate_bps, 0)?;
let t_hs_prepare = derive_single(SPEC_HS_PREPARE, hs_bit_rate_bps, 0)?;
let t_hs_zero = derive_single(
SPEC_HS_PREPARE_PLUS_ZERO,
hs_bit_rate_bps,
u64::from(t_hs_prepare),
)?;
let t_hs_trail = derive_single(SPEC_HS_TRAIL, hs_bit_rate_bps, 0)?;
let t_hs_exit = derive_single(SPEC_HS_EXIT, hs_bit_rate_bps, 0)?;
let t_clk_prepare = derive_single(SPEC_CLK_PREPARE, hs_bit_rate_bps, 0)?;
let t_clk_zero = derive_single(
SPEC_CLK_PREPARE_PLUS_ZERO,
hs_bit_rate_bps,
u64::from(t_clk_prepare),
)?;
let t_clk_pre = derive_single(SPEC_CLK_PRE, hs_bit_rate_bps, 0)?;
let t_clk_post = derive_single(SPEC_CLK_POST, hs_bit_rate_bps, 0)?;
let t_clk_trail = derive_single(SPEC_CLK_TRAIL, hs_bit_rate_bps, 0)?;
let timing = G8lS549DphyTimingUi {
t_lpx,
t_hs_prepare,
t_hs_zero,
t_hs_trail,
t_hs_exit,
t_clk_prepare,
t_clk_zero,
t_clk_pre,
t_clk_post,
t_clk_trail,
};
validate_s549_dphy_timing(timing, hs_bit_rate_bps)?;
Ok(timing)
}
fn check_range(index: usize, value_ui: u64, hs_bit_rate_bps: u64) -> S549Result<()> {
let spec = S549_DPHY_SPEC_RANGES[index];
if value_ui < s549_spec_min_ui(spec, hs_bit_rate_bps)? {
return Err(G8lS549DsiHostError::TimingBelowSpecMinimum);
}
if let Some(max) = s549_spec_max_ui(spec, hs_bit_rate_bps)? {
if value_ui > max {
return Err(G8lS549DsiHostError::TimingAboveSpecMaximum);
}
}
Ok(())
}
/// Re-checks every single and composite spec row for an arbitrary timing.
pub fn validate_s549_dphy_timing(
timing: G8lS549DphyTimingUi,
hs_bit_rate_bps: u64,
) -> S549Result<()> {
let t = timing;
let singles = [
t.t_lpx,
t.t_hs_prepare,
t.t_hs_zero,
t.t_hs_trail,
t.t_hs_exit,
t.t_clk_prepare,
t.t_clk_zero,
t.t_clk_pre,
t.t_clk_post,
t.t_clk_trail,
];
if singles
.iter()
.any(|value| u64::from(*value) % S549_UI_PER_BYTE_CLOCK != 0)
{
return Err(G8lS549DsiHostError::TimingNotByteClockAligned);
}
check_range(SPEC_LPX, u64::from(t.t_lpx), hs_bit_rate_bps)?;
check_range(SPEC_HS_PREPARE, u64::from(t.t_hs_prepare), hs_bit_rate_bps)?;
check_range(
SPEC_HS_PREPARE_PLUS_ZERO,
checked_add(u64::from(t.t_hs_prepare), u64::from(t.t_hs_zero))?,
hs_bit_rate_bps,
)?;
check_range(SPEC_HS_TRAIL, u64::from(t.t_hs_trail), hs_bit_rate_bps)?;
check_range(SPEC_HS_EXIT, u64::from(t.t_hs_exit), hs_bit_rate_bps)?;
check_range(
SPEC_CLK_PREPARE,
u64::from(t.t_clk_prepare),
hs_bit_rate_bps,
)?;
check_range(
SPEC_CLK_PREPARE_PLUS_ZERO,
checked_add(u64::from(t.t_clk_prepare), u64::from(t.t_clk_zero))?,
hs_bit_rate_bps,
)?;
check_range(SPEC_CLK_PRE, u64::from(t.t_clk_pre), hs_bit_rate_bps)?;
check_range(SPEC_CLK_POST, u64::from(t.t_clk_post), hs_bit_rate_bps)?;
check_range(SPEC_CLK_TRAIL, u64::from(t.t_clk_trail), hs_bit_rate_bps)
}
fn byte_clocks_field(ui: u32) -> S549Result<u32> {
let bytes = u64::from(ui) / S549_UI_PER_BYTE_CLOCK;
if bytes > 0xFF {
return Err(G8lS549DsiHostError::RegisterFieldOverflow);
}
Ok(bytes as u32)
}
fn pack_bytes(fields: [u32; 4]) -> S549Result<u32> {
let mut word = 0u32;
for (shift, field) in [0u32, 8, 16, 24].into_iter().zip(fields) {
word |= byte_clocks_field(field)? << shift;
}
Ok(word)
}
fn pack_blanking(sync: u32, back: u32, front: u32) -> S549Result<u32> {
for field in [sync, back, front] {
if field > S549_MAX_BLANKING_PIXELS {
return Err(G8lS549DsiHostError::RegisterFieldOverflow);
}
}
Ok(sync | (back << 10) | (front << 20))
}
fn bpp_code(bits_per_pixel: u8) -> S549Result<u32> {
match bits_per_pixel {
16 => Ok(0),
18 => Ok(1),
24 => Ok(2),
_ => Err(G8lS549DsiHostError::UnsupportedBitsPerPixel),
}
}
/// Encodes the full bring-up program: disable, lane/clock/PHY/video timing
/// configuration, interrupt enable, then enable in video mode.
pub fn encode_s549_register_program(
map: &[G8lS549DsiRegister],
mode: G8lS549PanelMode,
clocks: G8lS549LinkClocks,
timing: G8lS549DphyTimingUi,
) -> S549Result<G8lS549RegisterProgram> {
validate_s549_register_map(map)?;
let hs_clk_mhz = to_u32(clocks.hs_bit_rate_bps / 1_000_000)?;
if hs_clk_mhz > 0xFFFF || mode.hactive > 0xFFFF || mode.vactive > 0xFFFF {
return Err(G8lS549DsiHostError::RegisterFieldOverflow);
}
let lane_cfg = u32::from(mode.lanes - 1) | (bpp_code(mode.bits_per_pixel)? << 4);
let mut program = G8lS549RegisterProgram::new();
program.push(map, "DSI_CTRL", 0)?;
program.push(map, "DSI_LANE_CFG", lane_cfg)?;
program.push(map, "DSI_HS_CLK", hs_clk_mhz)?;
program.push(
map,
"DSI_PHY_TIMING_HS",
pack_bytes([
timing.t_lpx,
timing.t_hs_prepare,
timing.t_hs_zero,
timing.t_hs_trail,
])?,
)?;
program.push(
map,
"DSI_PHY_TIMING_CLK",
pack_bytes([
timing.t_clk_prepare,
timing.t_clk_zero,
timing.t_clk_pre,
timing.t_clk_post,
])?,
)?;
program.push(
map,
"DSI_PHY_TIMING_CLK2",
pack_bytes([timing.t_clk_trail, timing.t_hs_exit, 0, 0])?,
)?;
program.push(map, "DSI_VID_TIMING_HACTIVE", mode.hactive)?;
program.push(
map,
"DSI_VID_TIMING_HBLANK",
pack_blanking(mode.hsync, mode.hbp, mode.hfp)?,
)?;
program.push(map, "DSI_VID_TIMING_VACTIVE", mode.vactive)?;
program.push(
map,
"DSI_VID_TIMING_VBLANK",
pack_blanking(mode.vsync, mode.vbp, mode.vfp)?,
)?;
program.push(map, "DSI_INT_ENABLE", S549_INT_ENABLE_PANEL_ERROR)?;
program.push(map, "DSI_VID_MODE_CFG", S549_VID_MODE_CFG_BURST)?;
// Ordering is a contract: the first write disables the host and the last
// write enables it in video mode, so no timing register changes while the
// (modelled) link is active.
program.push(map, "DSI_CTRL", S549_CTRL_ENABLE | S549_CTRL_VIDEO_MODE)?;
if program.writes().len() != S549_PROGRAM_WRITE_COUNT {
return Err(G8lS549DsiHostError::ProgramBufferOverflow);
}
Ok(program)
}
/// Pure model service: validates the mode, derives clocks and D-PHY timing,
/// encodes the bounded register program, and publishes or retains a receipt.
pub fn service_s549_model_dsi_host_bring_up(
state: &mut G8lS549DsiHostState,
profile: G8lS549PanelProfile,
mode: G8lS549PanelMode,
) -> Result<G8lS549DsiHostOutcome, G8lS549DsiHostError> {
validate_s549_register_map(&S549_DSI_REGISTER_MAP)?;
let clocks = compute_s549_link_clocks(mode)?;
let timing = derive_s549_dphy_timing(clocks.hs_bit_rate_bps)?;
let program = encode_s549_register_program(&S549_DSI_REGISTER_MAP, mode, clocks, timing)?;
let receipt = G8lS549DsiHostReceipt {
sequence: S549_SEQUENCE,
predecessor_sequence: S549_EXPECTED_PREDECESSOR,
r1_stage: S549_R1_STAGE,
profile_id: profile.id(),
mode,
htotal: clocks.htotal,
vtotal: clocks.vtotal,
pixel_clock_hz: clocks.pixel_clock_hz,
hs_bit_rate_bps: clocks.hs_bit_rate_bps,
byte_clock_hz: clocks.byte_clock_hz,
ui_ps: clocks.ui_ps,
timing,
program_write_count: program.writes().len(),
program_checksum: program.checksum(),
register_offsets_hardware_validated: S549_REGISTER_OFFSETS_HARDWARE_VALIDATED,
s540_physical_verdict_retained_red: S549_S540_PHYSICAL_VERDICT_RETAINED_RED,
s543_physical_verdict_retained_red: S549_S543_PHYSICAL_VERDICT_RETAINED_RED,
automatic_promotion: S549_AUTOMATIC_PROMOTION,
hardware_present: S549_HARDWARE_PRESENT,
supported_profile_runtime_observations: S549_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS,
physical_observations: S549_PHYSICAL_OBSERVATIONS,
runbook_executed: RUNBOOK_EXECUTED_IN_S549,
};
if let Some(published) = state.receipt {
if published != receipt {
return Err(G8lS549DsiHostError::PublishedStateDrift);
}
return Ok(G8lS549DsiHostOutcome::Retained(published));
}
state.receipt = Some(receipt);
Ok(G8lS549DsiHostOutcome::Published(receipt))
}
snippet sha256: 5588fe963747…file sha256: 5588fe963747…
02 · Doğrulayan test kodu
Operations komutuna bağlı focused test
tam dosyaL1–L624
simulation/tests/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s549_r1_rp1_dsi_host_register_map_dphy_timing_model.rs::S549 r1 rp1 dsi host register map dphy timing model focused tests
use aselsan_microkernel_simulation::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s549_r1_rp1_dsi_host_register_map_dphy_timing_model::*;
use std::collections::BTreeSet;
const SOURCE: &str = include_str!(
"../../kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s549_r1_rp1_dsi_host_register_map_dphy_timing_model.rs"
);
const MAIN: &str = include_str!("../../kernel/src/main.rs");
const SIMULATION_LIB: &str = include_str!("../src/lib.rs");
const PHONE: G8lS549PanelProfile = G8lS549PanelProfile::Phone720x1280Lanes2;
const TABLET: G8lS549PanelProfile = G8lS549PanelProfile::Tablet1200x1920Lanes4;
const PHONE_PIXEL_CLOCK_HZ: u64 = 810 * 1304 * 60;
const PHONE_HS_BIT_RATE_BPS: u64 = PHONE_PIXEL_CLOCK_HZ * 24 / 2;
const TABLET_PIXEL_CLOCK_HZ: u64 = 1340 * 1964 * 60;
const TABLET_HS_BIT_RATE_BPS: u64 = TABLET_PIXEL_CLOCK_HZ * 24 / 4;
fn publish(
state: &mut G8lS549DsiHostState,
profile: G8lS549PanelProfile,
) -> Result<G8lS549DsiHostOutcome, G8lS549DsiHostError> {
service_s549_model_dsi_host_bring_up(state, profile, canonical_s549_panel_mode(profile))
}
fn phone_mode() -> G8lS549PanelMode {
canonical_s549_panel_mode(PHONE)
}
fn low_rate_mode() -> G8lS549PanelMode {
G8lS549PanelMode {
hactive: 320,
hfp: 8,
hbp: 8,
hsync: 4,
vactive: 240,
vfp: 4,
vbp: 4,
vsync: 2,
refresh_hz: 60,
lanes: 1,
bits_per_pixel: 16,
}
}
#[test]
fn sequence_scope_and_nonpromotion_are_exact() {
assert_eq!(S549_SEQUENCE, 549);
assert_eq!(S549_EXPECTED_PREDECESSOR, 548);
assert_eq!(S549_R1_STAGE, 2);
assert_eq!(S549_R1_RANGE_FIRST, 536);
assert_eq!(S549_R1_RANGE_LAST, 568);
assert_eq!(S549_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS, 0);
assert_eq!(S549_PHYSICAL_OBSERVATIONS, 0);
assert_eq!(S549_PHYSICAL_OR_DEVICE_OPERATIONS, 0);
assert_eq!(S549_SD_WRITES, 0);
assert_eq!(S549_UART_OPENS, 0);
assert_eq!(S549_POWER_TRANSITIONS, 0);
assert_eq!(S549_NEW_IMMUTABLE_RAW_CAPTURES, 0);
assert!(S549_S540_PHYSICAL_VERDICT_RETAINED_RED);
assert!(S549_S543_PHYSICAL_VERDICT_RETAINED_RED);
assert!(!S549_AUTOMATIC_PROMOTION);
assert!(!S549_BOOT_TO_UI_PHYSICALLY_OBSERVED);
assert!(!S549_HARDWARE_PRESENT);
assert!(!S549_R1_ACCEPTANCE_COMPLETE);
assert!(!RUNBOOK_EXECUTED_IN_S549);
assert!(!S549_REGISTER_OFFSETS_HARDWARE_VALIDATED);
assert_eq!(S549_SUPPORTED_LANE_COUNTS, [1, 2, 4]);
assert_eq!(S549_PROGRAM_WRITE_COUNT, 13);
assert!(S549_PROGRAM_WRITE_COUNT <= S549_MAX_PROGRAM_WRITES);
}
#[test]
fn module_is_registered_in_kernel_and_simulation() {
let module = "g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s549_r1_rp1_dsi_host_register_map_dphy_timing_model";
assert!(MAIN.contains(&format!("mod {module};")));
assert!(SIMULATION_LIB.contains(&format!("pub mod {module};")));
}
#[test]
fn source_has_no_device_execution_or_uart_emission_surface() {
for forbidden in [
"unsafe",
"asm!",
"write_volatile",
"crate::uart",
"crate::arch",
"#[no_mangle]",
"spin::",
"std::",
"crate::kprintln!",
] {
assert!(!SOURCE.contains(forbidden), "forbidden token: {forbidden}");
}
assert!(SOURCE.contains("NOT hardware-validated"));
assert!(SOURCE.contains("performs no device operation"));
assert!(SOURCE.contains("does not rerun S540 or S543"));
}
#[test]
fn diagnostic_codes_are_nonzero_and_unique() {
let errors = [
G8lS549DsiHostError::UnsupportedLaneCount,
G8lS549DsiHostError::UnsupportedBitsPerPixel,
G8lS549DsiHostError::ZeroDimension,
G8lS549DsiHostError::ActiveOutOfRange,
G8lS549DsiHostError::BlankingOutOfRange,
G8lS549DsiHostError::ArithmeticOverflow,
G8lS549DsiHostError::HsBitRateNotLaneDivisible,
G8lS549DsiHostError::HsBitRateBelowMinimum,
G8lS549DsiHostError::HsBitRateAboveMaximum,
G8lS549DsiHostError::TimingBelowSpecMinimum,
G8lS549DsiHostError::TimingAboveSpecMaximum,
G8lS549DsiHostError::TimingNotByteClockAligned,
G8lS549DsiHostError::RegisterMapMalformed,
G8lS549DsiHostError::RegisterUnknown,
G8lS549DsiHostError::RegisterNotWritable,
G8lS549DsiHostError::RegisterValueExceedsWidth,
G8lS549DsiHostError::RegisterFieldOverflow,
G8lS549DsiHostError::ProgramBufferOverflow,
G8lS549DsiHostError::PublishedStateDrift,
];
let codes: BTreeSet<_> = errors
.into_iter()
.map(G8lS549DsiHostError::diagnostic_code)
.collect();
assert_eq!(codes.len(), errors.len());
assert!(!codes.contains(&0));
}
#[test]
fn exact_replay_retains_the_same_receipt() {
let mut state = G8lS549DsiHostState::new();
let G8lS549DsiHostOutcome::Published(receipt) = publish(&mut state, PHONE).unwrap() else {
panic!("first publication missing")
};
assert_eq!(state.receipt(), Some(receipt));
assert_eq!(
publish(&mut state, PHONE),
Ok(G8lS549DsiHostOutcome::Retained(receipt))
);
}
#[test]
fn divergent_input_after_publication_fails_closed() {
let mut state = G8lS549DsiHostState::new();
publish(&mut state, PHONE).unwrap();
assert_eq!(
publish(&mut state, TABLET),
Err(G8lS549DsiHostError::PublishedStateDrift)
);
let mut drifted = phone_mode();
drifted.hfp += 1;
assert_eq!(
service_s549_model_dsi_host_bring_up(&mut state, PHONE, drifted),
Err(G8lS549DsiHostError::PublishedStateDrift)
);
}
#[test]
fn phone_profile_computes_expected_clocks_and_receipt() {
let mut state = G8lS549DsiHostState::default();
let G8lS549DsiHostOutcome::Published(receipt) = publish(&mut state, PHONE).unwrap() else {
panic!("phone publication missing")
};
assert_eq!(receipt.sequence, 549);
assert_eq!(receipt.predecessor_sequence, 548);
assert_eq!(receipt.r1_stage, 2);
assert_eq!(receipt.profile_id, 1);
assert_eq!(receipt.htotal, 810);
assert_eq!(receipt.vtotal, 1304);
assert_eq!(receipt.pixel_clock_hz, 63_374_400);
assert_eq!(receipt.pixel_clock_hz, PHONE_PIXEL_CLOCK_HZ);
assert_eq!(receipt.hs_bit_rate_bps, 760_492_800);
assert_eq!(receipt.hs_bit_rate_bps, PHONE_HS_BIT_RATE_BPS);
assert_eq!(receipt.byte_clock_hz, 95_061_600);
assert_eq!(receipt.ui_ps, 1314);
assert_eq!(receipt.program_write_count, 13);
assert!(!receipt.register_offsets_hardware_validated);
assert!(receipt.s540_physical_verdict_retained_red);
assert!(receipt.s543_physical_verdict_retained_red);
assert!(!receipt.automatic_promotion);
assert!(!receipt.hardware_present);
assert_eq!(receipt.supported_profile_runtime_observations, 0);
assert_eq!(receipt.physical_observations, 0);
assert!(!receipt.runbook_executed);
}
#[test]
fn phone_profile_derives_expected_dphy_timing_in_ui() {
let timing = derive_s549_dphy_timing(PHONE_HS_BIT_RATE_BPS).unwrap();
assert_eq!(
timing,
G8lS549DphyTimingUi {
t_lpx: 40,
t_hs_prepare: 40,
t_hs_zero: 88,
t_hs_trail: 56,
t_hs_exit: 80,
t_clk_prepare: 32,
t_clk_zero: 200,
t_clk_pre: 8,
t_clk_post: 104,
t_clk_trail: 48,
}
);
assert_eq!(validate_s549_dphy_timing(timing, PHONE_HS_BIT_RATE_BPS), Ok(()));
}
#[test]
fn tablet_profile_uses_four_lanes_within_spec() {
let mut state = G8lS549DsiHostState::new();
let G8lS549DsiHostOutcome::Published(receipt) = publish(&mut state, TABLET).unwrap() else {
panic!("tablet publication missing")
};
assert_eq!(receipt.profile_id, 2);
assert_eq!(receipt.mode.lanes, 4);
assert_eq!(receipt.htotal, 1340);
assert_eq!(receipt.vtotal, 1964);
assert_eq!(receipt.pixel_clock_hz, TABLET_PIXEL_CLOCK_HZ);
assert_eq!(receipt.hs_bit_rate_bps, 947_433_600);
assert_eq!(receipt.hs_bit_rate_bps, TABLET_HS_BIT_RATE_BPS);
assert_eq!(receipt.byte_clock_hz, 118_429_200);
assert_eq!(receipt.ui_ps, 1055);
assert!(receipt.hs_bit_rate_bps <= S549_MAX_HS_BIT_RATE_BPS);
assert_eq!(
validate_s549_dphy_timing(receipt.timing, receipt.hs_bit_rate_bps),
Ok(())
);
}
#[test]
fn timing_values_are_byte_clock_aligned_and_within_spec_for_all_lane_counts() {
for lanes in S549_SUPPORTED_LANE_COUNTS {
let mut mode = phone_mode();
mode.lanes = lanes;
mode.bits_per_pixel = if lanes == 1 { 16 } else { 24 };
let clocks = compute_s549_link_clocks(mode).unwrap();
let timing = derive_s549_dphy_timing(clocks.hs_bit_rate_bps).unwrap();
for value in [
timing.t_lpx,
timing.t_hs_prepare,
timing.t_hs_zero,
timing.t_hs_trail,
timing.t_hs_exit,
timing.t_clk_prepare,
timing.t_clk_zero,
timing.t_clk_pre,
timing.t_clk_post,
timing.t_clk_trail,
] {
assert_eq!(u64::from(value) % S549_UI_PER_BYTE_CLOCK, 0);
assert!(value > 0);
}
assert_eq!(validate_s549_dphy_timing(timing, clocks.hs_bit_rate_bps), Ok(()));
}
}
#[test]
fn one_lane_phone_mode_exceeds_hs_bit_rate_ceiling() {
let mut mode = phone_mode();
mode.lanes = 1;
assert_eq!(
compute_s549_link_clocks(mode),
Err(G8lS549DsiHostError::HsBitRateAboveMaximum)
);
let mut state = G8lS549DsiHostState::new();
assert_eq!(
service_s549_model_dsi_host_bring_up(&mut state, PHONE, mode),
Err(G8lS549DsiHostError::HsBitRateAboveMaximum)
);
assert_eq!(state.receipt(), None);
}
#[test]
fn unsupported_lane_counts_and_pixel_depths_are_rejected() {
for lanes in [0, 3, 5, 8] {
let mut mode = phone_mode();
mode.lanes = lanes;
assert_eq!(
validate_s549_panel_mode(mode),
Err(G8lS549DsiHostError::UnsupportedLaneCount),
"lanes {lanes}"
);
}
for bpp in [0, 12, 30, 32] {
let mut mode = phone_mode();
mode.bits_per_pixel = bpp;
assert_eq!(
validate_s549_panel_mode(mode),
Err(G8lS549DsiHostError::UnsupportedBitsPerPixel),
"bpp {bpp}"
);
}
}
#[test]
fn zero_or_out_of_range_geometry_is_rejected() {
let mut zero = phone_mode();
zero.vactive = 0;
assert_eq!(
validate_s549_panel_mode(zero),
Err(G8lS549DsiHostError::ZeroDimension)
);
let mut no_refresh = phone_mode();
no_refresh.refresh_hz = 0;
assert_eq!(
validate_s549_panel_mode(no_refresh),
Err(G8lS549DsiHostError::ZeroDimension)
);
let mut wide = phone_mode();
wide.hactive = S549_MAX_ACTIVE_PIXELS + 1;
assert_eq!(
validate_s549_panel_mode(wide),
Err(G8lS549DsiHostError::ActiveOutOfRange)
);
let mut blank = phone_mode();
blank.vbp = S549_MAX_BLANKING_PIXELS + 1;
assert_eq!(
validate_s549_panel_mode(blank),
Err(G8lS549DsiHostError::BlankingOutOfRange)
);
let mut zero_blank = phone_mode();
zero_blank.hsync = 0;
assert_eq!(
validate_s549_panel_mode(zero_blank),
Err(G8lS549DsiHostError::BlankingOutOfRange)
);
}
#[test]
fn pixel_clock_overflow_fails_closed() {
let mut mode = phone_mode();
mode.refresh_hz = u64::MAX;
assert_eq!(
compute_s549_link_clocks(mode),
Err(G8lS549DsiHostError::ArithmeticOverflow)
);
mode.refresh_hz = u64::MAX / (810 * 1304);
assert_eq!(
compute_s549_link_clocks(mode),
Err(G8lS549DsiHostError::ArithmeticOverflow)
);
}
#[test]
fn lane_indivisible_link_rate_and_low_rate_are_rejected() {
let odd = G8lS549PanelMode {
hactive: 101,
hfp: 1,
hbp: 1,
hsync: 2,
vactive: 101,
vfp: 1,
vbp: 1,
vsync: 2,
refresh_hz: 61,
lanes: 4,
bits_per_pixel: 18,
};
assert_eq!(
compute_s549_link_clocks(odd),
Err(G8lS549DsiHostError::HsBitRateNotLaneDivisible)
);
let mut slow = low_rate_mode();
slow.refresh_hz = 30;
assert_eq!(
compute_s549_link_clocks(slow),
Err(G8lS549DsiHostError::HsBitRateBelowMinimum)
);
}
#[test]
fn low_rate_mode_violates_clk_prepare_maximum() {
let clocks = compute_s549_link_clocks(low_rate_mode()).unwrap();
assert_eq!(clocks.hs_bit_rate_bps, 81_600_000);
assert_eq!(
derive_s549_dphy_timing(clocks.hs_bit_rate_bps),
Err(G8lS549DsiHostError::TimingAboveSpecMaximum)
);
let mut state = G8lS549DsiHostState::new();
assert_eq!(
service_s549_model_dsi_host_bring_up(&mut state, PHONE, low_rate_mode()),
Err(G8lS549DsiHostError::TimingAboveSpecMaximum)
);
assert_eq!(state.receipt(), None);
}
#[test]
fn handcrafted_timing_outside_spec_is_rejected() {
let good = derive_s549_dphy_timing(PHONE_HS_BIT_RATE_BPS).unwrap();
let mut short_lpx = good;
short_lpx.t_lpx = 32;
assert_eq!(
validate_s549_dphy_timing(short_lpx, PHONE_HS_BIT_RATE_BPS),
Err(G8lS549DsiHostError::TimingBelowSpecMinimum)
);
let mut long_prepare = good;
long_prepare.t_hs_prepare = 72;
assert_eq!(
validate_s549_dphy_timing(long_prepare, PHONE_HS_BIT_RATE_BPS),
Err(G8lS549DsiHostError::TimingAboveSpecMaximum)
);
let mut short_composite = good;
short_composite.t_hs_zero = 72;
assert_eq!(
validate_s549_dphy_timing(short_composite, PHONE_HS_BIT_RATE_BPS),
Err(G8lS549DsiHostError::TimingBelowSpecMinimum)
);
let mut unaligned = good;
unaligned.t_clk_trail = 49;
assert_eq!(
validate_s549_dphy_timing(unaligned, PHONE_HS_BIT_RATE_BPS),
Err(G8lS549DsiHostError::TimingNotByteClockAligned)
);
}
#[test]
fn dphy_spec_table_encodes_consistent_min_max_ranges() {
assert_eq!(S549_DPHY_SPEC_RANGES.len(), S549_DPHY_SPEC_RANGE_COUNT);
let names: BTreeSet<_> = S549_DPHY_SPEC_RANGES.iter().map(|r| r.name).collect();
assert_eq!(names.len(), S549_DPHY_SPEC_RANGE_COUNT);
for range in S549_DPHY_SPEC_RANGES {
for rate in [S549_MIN_HS_BIT_RATE_BPS, PHONE_HS_BIT_RATE_BPS, S549_MAX_HS_BIT_RATE_BPS] {
let min = s549_spec_min_ui(range, rate).unwrap();
if let Some(max) = s549_spec_max_ui(range, rate).unwrap() {
assert!(min <= max, "{} at {rate}", range.name);
}
}
}
let prepare = S549_DPHY_SPEC_RANGES[1];
assert_eq!(prepare.name, "T_HS_PREPARE");
assert_eq!(s549_spec_min_ui(prepare, PHONE_HS_BIT_RATE_BPS), Ok(35));
assert_eq!(s549_spec_max_ui(prepare, PHONE_HS_BIT_RATE_BPS), Ok(Some(70)));
assert_eq!(s549_spec_min_ui(prepare, u64::MAX), Err(G8lS549DsiHostError::ArithmeticOverflow));
}
#[test]
fn register_map_is_sorted_aligned_unique_and_symbolic() {
assert_eq!(validate_s549_register_map(&S549_DSI_REGISTER_MAP), Ok(()));
assert_eq!(S549_DSI_REGISTER_MAP.len(), S549_REGISTER_COUNT);
for name in [
"DSI_CTRL",
"DSI_HS_CLK",
"DSI_LANE_CFG",
"DSI_PKT_HDR",
"DSI_VID_TIMING_HACTIVE",
"DSI_VID_TIMING_HBLANK",
"DSI_VID_TIMING_VACTIVE",
"DSI_VID_TIMING_VBLANK",
] {
let register = lookup_s549_register(&S549_DSI_REGISTER_MAP, name).unwrap();
assert_eq!(register.width_bits, 32);
assert_eq!(register.offset % 4, 0);
}
assert_eq!(
lookup_s549_register(&S549_DSI_REGISTER_MAP, "DSI_STATUS").unwrap().access,
G8lS549RegisterAccess::ReadOnly
);
assert_eq!(
lookup_s549_register(&S549_DSI_REGISTER_MAP, "DSI_ID").unwrap().reset_value,
0x4453_4931
);
assert_eq!(
lookup_s549_register(&S549_DSI_REGISTER_MAP, "DSI_MISSING"),
Err(G8lS549DsiHostError::RegisterUnknown)
);
}
#[test]
fn malformed_register_maps_are_rejected() {
assert_eq!(
validate_s549_register_map(&[]),
Err(G8lS549DsiHostError::RegisterMapMalformed)
);
let mut unaligned = S549_DSI_REGISTER_MAP;
unaligned[4].offset = 0x012;
assert_eq!(
validate_s549_register_map(&unaligned),
Err(G8lS549DsiHostError::RegisterMapMalformed)
);
let mut overlapping = S549_DSI_REGISTER_MAP;
overlapping[2].offset = 0x004;
assert_eq!(
validate_s549_register_map(&overlapping),
Err(G8lS549DsiHostError::RegisterMapMalformed)
);
let mut duplicate = S549_DSI_REGISTER_MAP;
duplicate[3].name = "DSI_CTRL";
assert_eq!(
validate_s549_register_map(&duplicate),
Err(G8lS549DsiHostError::RegisterMapMalformed)
);
let mut narrow = S549_DSI_REGISTER_MAP;
narrow[0].width_bits = 16;
assert_eq!(
validate_s549_register_map(&narrow),
Err(G8lS549DsiHostError::RegisterMapMalformed)
);
}
#[test]
fn register_program_rejects_unknown_readonly_and_oversized_writes() {
let mut program = G8lS549RegisterProgram::new();
assert_eq!(
program.push(&S549_DSI_REGISTER_MAP, "DSI_NOPE", 1),
Err(G8lS549DsiHostError::RegisterUnknown)
);
assert_eq!(
program.push(&S549_DSI_REGISTER_MAP, "DSI_STATUS", 1),
Err(G8lS549DsiHostError::RegisterNotWritable)
);
assert_eq!(
program.push(&S549_DSI_REGISTER_MAP, "DSI_ID", 1),
Err(G8lS549DsiHostError::RegisterNotWritable)
);
let mut narrow = S549_DSI_REGISTER_MAP;
narrow[0].width_bits = 8;
assert_eq!(
program.push(&narrow, "DSI_CTRL", 0x100),
Err(G8lS549DsiHostError::RegisterValueExceedsWidth)
);
assert_eq!(program.writes().len(), 0);
assert_eq!(
program.push(&S549_DSI_REGISTER_MAP, "DSI_INT_STATUS", 0xFFFF_FFFF),
Ok(G8lS549RegisterWrite {
offset: 0x008,
value: 0xFFFF_FFFF
})
);
}
#[test]
fn register_program_buffer_is_bounded() {
let mut program = G8lS549RegisterProgram::new();
for _ in 0..S549_MAX_PROGRAM_WRITES {
program.push(&S549_DSI_REGISTER_MAP, "DSI_PKT_PAYLOAD", 0xAB).unwrap();
}
assert_eq!(program.writes().len(), S549_MAX_PROGRAM_WRITES);
assert_eq!(
program.push(&S549_DSI_REGISTER_MAP, "DSI_PKT_PAYLOAD", 0xAB),
Err(G8lS549DsiHostError::ProgramBufferOverflow)
);
assert_eq!(program.writes().len(), S549_MAX_PROGRAM_WRITES);
}
#[test]
fn encoded_program_disables_first_configures_then_enables_video_mode() {
let mode = phone_mode();
let clocks = compute_s549_link_clocks(mode).unwrap();
let timing = derive_s549_dphy_timing(clocks.hs_bit_rate_bps).unwrap();
let program = encode_s549_register_program(&S549_DSI_REGISTER_MAP, mode, clocks, timing).unwrap();
let writes = program.writes();
assert_eq!(writes.len(), S549_PROGRAM_WRITE_COUNT);
assert_eq!(writes[0], G8lS549RegisterWrite { offset: 0x000, value: 0 });
assert_eq!(writes[1], G8lS549RegisterWrite { offset: 0x014, value: 0x21 });
assert_eq!(writes[2], G8lS549RegisterWrite { offset: 0x010, value: 760 });
// lpx=40/8=5, prepare=5, zero=11, trail=7 byte clocks.
assert_eq!(writes[3], G8lS549RegisterWrite { offset: 0x018, value: 0x070B_0505 });
// clk_prepare=4, clk_zero=25, clk_pre=1, clk_post=13.
assert_eq!(writes[4], G8lS549RegisterWrite { offset: 0x01C, value: 0x0D01_1904 });
// clk_trail=6, hs_exit=10.
assert_eq!(writes[5], G8lS549RegisterWrite { offset: 0x020, value: 0x0000_0A06 });
assert_eq!(writes[6], G8lS549RegisterWrite { offset: 0x030, value: 720 });
assert_eq!(
writes[7],
G8lS549RegisterWrite { offset: 0x034, value: 10 | (40 << 10) | (40 << 20) }
);
assert_eq!(writes[8], G8lS549RegisterWrite { offset: 0x038, value: 1280 });
assert_eq!(
writes[9],
G8lS549RegisterWrite { offset: 0x03C, value: 4 | (10 << 10) | (10 << 20) }
);
assert_eq!(writes[10], G8lS549RegisterWrite { offset: 0x00C, value: 1 });
assert_eq!(writes[11], G8lS549RegisterWrite { offset: 0x040, value: 1 });
assert_eq!(writes[12], G8lS549RegisterWrite { offset: 0x000, value: 0b11 });
let mut state = G8lS549DsiHostState::new();
let G8lS549DsiHostOutcome::Published(receipt) = publish(&mut state, PHONE).unwrap() else {
panic!("phone publication missing")
};
assert_eq!(receipt.program_checksum, program.checksum());
assert_ne!(receipt.program_checksum, 0);
let mut other = G8lS549DsiHostState::new();
let G8lS549DsiHostOutcome::Published(tablet) = publish(&mut other, TABLET).unwrap() else {
panic!("tablet publication missing")
};
assert_ne!(tablet.program_checksum, receipt.program_checksum);
}
#[test]
fn encoder_rejects_register_field_overflow_and_malformed_map() {
let mode = phone_mode();
let clocks = compute_s549_link_clocks(mode).unwrap();
let timing = derive_s549_dphy_timing(clocks.hs_bit_rate_bps).unwrap();
let mut oversized = timing;
oversized.t_clk_zero = 256 * 8;
assert_eq!(
encode_s549_register_program(&S549_DSI_REGISTER_MAP, mode, clocks, oversized),
Err(G8lS549DsiHostError::RegisterFieldOverflow)
);
let mut broken = S549_DSI_REGISTER_MAP;
broken[1].offset = 0x000;
assert_eq!(
encode_s549_register_program(&broken, mode, clocks, timing),
Err(G8lS549DsiHostError::RegisterMapMalformed)
);
let mut missing = S549_DSI_REGISTER_MAP;
missing[6].name = "DSI_RENAMED";
assert_eq!(
encode_s549_register_program(&missing, mode, clocks, timing),
Err(G8lS549DsiHostError::RegisterUnknown)
);
}
#[test]
fn source_only_gate_keeps_runtime_physical_and_r1_claims_zero() {
assert!(SOURCE.contains("S549_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0"));
assert!(SOURCE.contains("S549_PHYSICAL_OBSERVATIONS: usize = 0"));
assert!(SOURCE.contains("S549_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0"));
assert!(SOURCE.contains("S549_HARDWARE_PRESENT: bool = false"));
assert!(SOURCE.contains("S549_R1_ACCEPTANCE_COMPLETE: bool = false"));
assert!(SOURCE.contains("RUNBOOK_EXECUTED_IN_S549: bool = false"));
assert!(SOURCE.contains("S549_REGISTER_OFFSETS_HARDWARE_VALIDATED: bool = false"));
}
snippet sha256: 85b605285628…file sha256: 85b605285628…
03 · Kapı kimlik kaydı
Operations sıra, kimlik ve başlık bağı
tam Operations kaydıL2962–L3026
website/src/lib/operations.ts::g8l-s549-r1-rp1-dsi-host-register-map-dphy-timing-model
{
id: "g8l-s549-r1-rp1-dsi-host-register-map-dphy-timing-model",
date: "2026-08-30",
sequence: 549,
status: "passed",
umbrella_status: "partial",
title: "S549 · R1 ekran: RP1 DSI host register haritası ve D-PHY zamanlama modeli",
summary:
"S549 kaynak kapısı PASS'tir: RP1 (Raspberry Pi 5 southbridge) MIPI DSI host bring-up'ı veri olarak modellendi; 17 girişli sembolik register haritası (DSI_CTRL, DSI_HS_CLK, DSI_LANE_CFG, DSI_PKT_HDR, DSI_VID_TIMING_*), piksel saati / lane / bpp'den checked aritmetikle byte clock ve HS bit hızı üreten hesaplayıcı, min/max kodlanmış D-PHY spec tablosundan UI biriminde T_LPX/T_HS_*/T_CLK_* türetimi ve 16 yazımla sınırlı register programı encoder'ı tek fail-closed modülde toplandı. 720x1280@60 RGB888 2-lane telefon profili 760492800 bps/lane, 1200x1920@60 RGB888 4-lane 10.1 inç tablet profili 947433600 bps/lane verir; 1-lane telefon modu 1.5 Gbps tavanını aştığı için, düşük hızlı 81.6 Mbps modu ise T_CLK_PREPARE maksimumunu ihlal ettiği için reddedilir. Register offset'leri RP1 datasheet'ine sonradan bağlanacak model placeholder'larıdır ve donanımda doğrulanmamıştır. Focused 25/25 PASS'tir. S540 ve S543 fiziksel RED immutable kalır, hiçbir panel/DSI host/board yoktur, physical observation=0 ve RUNBOOK_EXECUTED_IN_S549=NO'dur. S550 aynı kaynak/host sınırında R1 aşama 2'yi sürdüren bir sonraki kapıdır.",
evidence: [
"S549, S548'den ayrı kaynak model modülü, 25-test focused binary, proof, status manifest, Operations kaydı ve complete Code kartına sahiptir; hiçbir üretim çağrı noktasına, boot/IRQ/scheduler/driver yoluna bağlanmamıştır.",
"Dar S549 source-model status=PASS; R1 umbrella=PARTIAL, S540 ve S543 physical gate status=RED olarak ayrı tutulur ve S546 üçüncü fiziksel koşunun kararı varsayılmaz.",
"Register haritası 17 sembolik 32-bit girişten oluşur: DSI_CTRL 0x000 RW, DSI_STATUS 0x004 RO, DSI_INT_STATUS 0x008 W1C, DSI_INT_ENABLE 0x00C RW, DSI_HS_CLK 0x010 RW, DSI_LANE_CFG 0x014 RW, DSI_PHY_TIMING_HS/CLK/CLK2 0x018-0x020 RW, DSI_PKT_HDR 0x024 WO, DSI_PKT_PAYLOAD 0x028 WO, DSI_VID_TIMING_HACTIVE/HBLANK/VACTIVE/VBLANK 0x030-0x03C RW, DSI_VID_MODE_CFG 0x040 RW ve DSI_ID 0x0FC RO (reset 0x44534931).",
"Offset, genişlik, reset ve erişim sınıfları RP1 datasheet'ine sonradan bağlanacak model placeholder'larıdır; donanımda doğrulanmamıştır ve S549_REGISTER_OFFSETS_HARDWARE_VALIDATED=false her receipt'te taşınır.",
"Harita doğrulayıcı boş tablo, 32-bit dışı genişlik, 4-byte hizasız veya artmayan/çakışan offset ve tekrarlanan isimleri RegisterMapMalformed ile reddeder.",
"Saat hesaplayıcı pixel_clock=htotal*vtotal*refresh, hs_bit_rate=pixel_clock*bpp/lanes (tam bölünme zorunlu), byte_clock=hs_bit_rate/8 ve ui_ps=1e12/hs_bit_rate değerlerini yalnız checked u64 aritmetikle üretir; taşma ArithmeticOverflow ile fail-closed döner.",
"Desteklenen lane sayıları 1/2/4, bpp 16/18/24, HS bit hızı aralığı 80 Mbps-1.5 Gbps, aktif piksel ≤4096 ve blanking 1..=1023'tür; bunların dışı UnsupportedLaneCount, UnsupportedBitsPerPixel, ZeroDimension, ActiveOutOfRange, BlankingOutOfRange, HsBitRateBelowMinimum veya HsBitRateAboveMaximum ile reddedilir.",
"Telefon profili 720x1280@60 RGB888 2-lane (htotal 810, vtotal 1304) pixel clock 63374400 Hz, HS bit hızı 760492800 bps/lane, byte clock 95061600 Hz ve UI 1314 ps verir.",
"Tablet profili 1200x1920@60 RGB888 4-lane (htotal 1340, vtotal 1964) pixel clock 157905600 Hz, HS bit hızı 947433600 bps/lane, byte clock 118429200 Hz ve UI 1055 ps verir.",
"D-PHY spec tablosu 10 satırda min (ns+UI) ve varsa max (ns+UI) kodlar: T_LPX≥50ns, T_HS_PREPARE 40ns+4UI..85ns+6UI, T_HS_PREPARE+T_HS_ZERO≥145ns+10UI, T_HS_TRAIL≥60ns+4UI, T_HS_EXIT≥100ns, T_CLK_PREPARE 38..95ns, T_CLK_PREPARE+T_CLK_ZERO≥300ns, T_CLK_PRE≥8UI, T_CLK_POST≥60ns+52UI, T_CLK_TRAIL≥60ns.",
"Türetilen zamanlamalar spec minimumunun byte-clock (8 UI) katına yuvarlanmasıdır; yuvarlanmış değer bir spec maksimumunu aşarsa kırpılmaz, TimingAboveSpecMaximum ile reddedilir. Telefon profili T_LPX=40, T_HS_PREPARE=40, T_HS_ZERO=88, T_HS_TRAIL=56, T_HS_EXIT=80, T_CLK_PREPARE=32, T_CLK_ZERO=200, T_CLK_PRE=8, T_CLK_POST=104, T_CLK_TRAIL=48 UI üretir.",
"Bağımsız doğrulayıcı elle kurulan zamanlamalarda spec minimum altını, maksimum üstünü, bileşik toplam ihlalini ve 8 UI hizasız değerleri ayrı diagnostic kodlarıyla reddeder.",
"Register programı en fazla 16 yazımla sınırlıdır ve kanonik program 13 yazımdır: önce DSI_CTRL=0, sonra lane/bpp, HS clock (MHz), üç paketlenmiş PHY timing sözcüğü, dört video timing sözcüğü, INT_ENABLE, VID_MODE_CFG ve en son DSI_CTRL=ENABLE|VIDEO_MODE.",
"Bilinmeyen register, salt-okunur register yazımı, genişliği aşan değer, 8-bit/10-bit alan taşması ve sınırı aşan program uzunluğu RegisterUnknown, RegisterNotWritable, RegisterValueExceedsWidth, RegisterFieldOverflow ve ProgramBufferOverflow ile fail-closed döner.",
"1-lane telefon modu 1520985600 bps ile 1.5 Gbps tavanını aştığından, 320x240 16-bpp 1-lane 81.6 Mbps modu ise byte-clock yuvarlanmış T_CLK_PREPARE değeri 95 ns maksimumunu aştığından reddedilir; her iki durumda state receipt'i boş kalır.",
"Exact replay aynı receipt ile Retained döner; yayından sonra farklı profil veya tek alanı değişmiş mode PublishedStateDrift ile reddedilir. 19 diagnostic kodu sıfırdan farklı ve benzersizdir.",
"Kaynakta unsafe, asm!, write_volatile, crate::uart, crate::arch, #[no_mangle], spin:: ve std:: yoktur; focused test bunu kaynak metni üzerinden doğrular.",
"Focused target 1 grup / 25 passed / 0 failed / 0 ignored / 0 filtered verdi.",
"Implementation 28348 B / 5588fe963747f3f560d92284aadb040a0d76bb414f9d84a51d493d2b528b5e15; focused test 22477 B / 85b60528562819fb4b6456cdbc1ebd36dac8c5bdaf61657032c60d99ae25533c SHA-256'dır.",
"Proof 6624 B'dir.",
"S540 immutable raw 20525 B ve S543 immutable raw 20509 B fiziksel RED olarak byte-exact korunur; automatic promotion=false ve S543 rerun=false'dur.",
"S549 sırasında panel, DSI host, D-PHY, board, SD write/read-back/eject, UART open/capture, power transition, fiziksel koşu veya yeni immutable raw üretimi yapılmadı.",
"RUNBOOK_EXECUTED_IN_S549=NO; supported-profile runtime observations=0, physical observations=0, hardware present=false, Boot-to-UI physically observed=false ve R1 acceptance=false'dur.",
"S550 aynı kaynak/host sınırında R1 aşama 2'yi sürdürür; register offset'lerinin RP1 datasheet'ine bağlanması ayrı ve açıkça belgelenecek bir adımdır, aygıt veya fiziksel koşu yetkisi değildir.",
],
commands: [
"CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s549_r1_rp1_dsi_host_register_map_dphy_timing_model -- --test-threads=1",
],
terminalSessions: [
{
id: "s549-focused",
title: "S549 RP1 DSI host register map ve D-PHY timing model focused",
commandLines: [
"CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s549_r1_rp1_dsi_host_register_map_dphy_timing_model -- --test-threads=1",
],
outputLines: [
"test result: ok. 25 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s",
"S549 focused=1 group / 25 passed / 0 failed",
"hardware=none physical=0 runbook=NO",
],
exitCode: 0,
outputMode: "complete",
},
],
terminalSessionsNote:
"S549 kaynak/host model PASS'tir; supported-profile runtime veya fiziksel PASS değildir. S540 ve S543 RED raw ve kararları değişmez.",
limitations: [
"S549 yalnız kaynak/host modelidir; hiçbir donanım/panel/modem/board gözlemi yoktur ve modül hiçbir üretim boot/IRQ/scheduler/driver yoluna bağlanmamıştır.",
"Register offset, genişlik, reset ve erişim sınıfları RP1 datasheet'ine bağlanmamış model placeholder'larıdır; donanımda doğrulanmamıştır.",
"D-PHY spec aralıkları modelde kodlanmış tablo değerleridir; gerçek D-PHY veya panel zamanlaması ölçülmemiştir.",
"S540 ve S543 fiziksel RED immutable kalır; S546 üçüncü fiziksel koşunun kararı bu kapıda varsayılmaz ve otomatik yükseltme yoktur.",
"BOOT_TO_UI_READY gerçek UART'ta görülmedi; Boot-to-UI ve R1 acceptance false kalır.",
"S550 aynı kaynak/host sınırında R1 aşama 2'yi sürdürür; yeni SD/UART/power koşusu ayrı kapı, fresh target revalidation, açık operatör yetkisi ve yeni immutable raw ister.",
],
},snippet sha256: 19ebdefd7075…file sha256: 9726dbf00f84…
Focused test komutu
CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s549_r1_rp1_dsi_host_register_map_dphy_timing_model -- --test-threads=1proof: docs/M8.1-RPi5-G8l-S549-R1-RP1-DSI-Host-Register-Map-DPhy-Timing-Model-Proof.md
Registry schema v5 · generator
website/scripts/generate-code-gates.mjs · Tam SHA-256: 3050638b71a684d8f8f947a8a6faa237a17fa8db5dc0db04fb207b668b462af9