32 KiB
MIPI D-PHY Display Flicker Investigation Suite
Specification for Claude Code — Build From Scratch
1. CONTEXT & BACKGROUND
This suite investigates infrequent vertical jitter/flicker and total display blackout on a custom PCB using a TI SN65DSI83 MIPI-DSI-to-LVDS bridge IC.
The LVDS output side has been validated with static test images and is considered healthy. The fault is upstream — on the MIPI D-PHY physical layer between the i.MX 8M Mini SoM and the bridge input.
Root Cause Hypothesis (from hardware engineer investigation)
The Linux mainline samsung-dsim driver uses "best-fit" (round-to-nearest) rounding when
converting D-PHY timing parameters from picoseconds to byte-clock cycles. Several parameters
consistently round below the MIPI D-PHY v1.1 specification minimums, causing the SN65DSI83
to occasionally fail to latch the Start-of-Transmission (SoT) sequence, producing a frame jump
or blackout.
Key violating parameters at 72 MHz pixel clock (432 Mbps DSI, byte clock = 54 MHz):
| Parameter | Spec Min (ps) | Mainline "Best Fit" (ps) | Violation? |
|---|---|---|---|
clk_prepare |
38,000 | 37,037 | YES |
clk_zero |
262,000* | 259,259 | YES |
clk_trail |
60,000 | 55,555 | YES |
hs_zero |
118,890 | 111,111 | YES |
hs_trail |
97,040 | 92,592 | YES |
hs_exit |
100,000 | 92,592 | YES |
*
clk_prepare + clk_zerocombined minimum is 300,000 ps per MIPI D-PHY v1.1 Table 14.
A "round-up" patch was applied (u-boot dsi-tweak bit 2) and reduces flicker frequency
but does not fully eliminate it. Additional per-parameter padding registers are available.
The goal of this suite is to automate stress testing across parameter combinations and correlate scope-captured D-PHY waveforms with bridge error registers to find the exact boundary condition that triggers a flicker event.
Prior Art: Falcon Board MIPI Analysis (S. Bouriot, May 2024)
A previous investigation on the Falcon board (different product, same SN65DSI83 bridge, lower pixel clock ~52 MHz) manually decoded MIPI DSI packets from scope captures during a spread-spectrum-induced shift fault. That analysis is directly applicable here. Key findings:
What was proven by direct packet decoding:
The fault manifests at the DSI packet payload layer, not just the PHY layer. Two distinct failure modes were identified by decoding Lane 0 data bytes:
Failure Mode A — Pixel Data Offset (Shifted Display):
- In a healthy frame, the first long packet (0x3E = Packed Pixel Stream, 24-bit RGB) contains the correct pixel colour bytes (e.g. 0xD1, 0xB5, 0x90) starting from byte 0 of the payload.
- In the fault state, the first long packet contains only 0x00 bytes. The correct pixel data (0xD1, 0xB5, 0x90) appears in the second long packet, offset into the line, not at the start.
- This means the bridge is rendering pixel N's data at pixel N+offset position — causing the visible horizontal/vertical shift.
- The packet header (0x21 H-Sync Start, 0x3E Data ID) is identical in both good and fault states — the fault is in the payload, not the framing.
Failure Mode B — Lane Stall (Flickering/Blackout):
- The data lane enters LP-11 (Stop state) for ~20 ms at ~20 ms intervals.
- No pixel data packets are transmitted during this period.
- This is not observed in the healthy display state — it is definitively abnormal.
- This corresponds to the "total blackout" failure mode seen on the Nexio board.
What the Falcon analysis could NOT determine:
- Whether the fault originates at the DSI transmitter (i.MX), the bridge input, or is a timing/PLL issue. The Falcon fault was triggered by spread spectrum; the Nexio fault occurs without spread spectrum, suggesting a different (or additional) root cause.
Implication for this suite: The analysis must operate at two levels simultaneously:
- PHY level — scope capture of LP/HS transitions, measuring T_HS_PREPARE etc.
- Packet level — decoding Lane 0 differential data to verify pixel bytes are in the correct position within each long packet.
The packet-level decode is the ground truth for whether a flicker event has occurred, independent of what the SN65DSI83 error registers report.
2. SYSTEM ARCHITECTURE
┌─────────────────────────────────────────────────────────┐
│ Host PC (Python 3.x) │
│ │
│ master_loop.py │
│ ├── TargetController → HTTP REST → 192.168.45.8 │
│ ├── ScopeController → VXI-11 → 192.168.45.4 │
│ └── PSUController → VXI-11 → 192.168.45.3 │
└─────────────────────────────────────────────────────────┘
Target (192.168.45.8:5000)
└── i.MX 8M Mini (Digi ConnectCore 8M Mini SoM)
├── samsung-dsim driver → MIPI D-PHY 4-lane
├── TI SN65DSI83 bridge → I2C bus 2 @ 0x2C
└── Flask REST server → /registers, /sn65_registers,
/sn65_settling, /display, /video
Scope (192.168.45.4)
└── Agilent/Keysight DSO80204B (2 GHz, LXI)
CH1: MIPI CLK+ (19.2× atten, DC 50Ω)
CH2: MIPI CLK- (19.2× atten, DC 50Ω)
CH3: MIPI DAT0+ (19.2× atten, DC 50Ω)
CH4: MIPI DAT0- (19.2× atten, DC 50Ω)
PSU (192.168.45.3)
└── Siglent SPD3303X-E (LXI, SCPI)
CH1: Display 3.3V rail
3. PROJECT FILE STRUCTURE
mipi_flicker/
├── SPEC.md ← This document
├── requirements.txt
├── config.py ← All constants, IPs, thresholds
├── hardware/
│ ├── __init__.py
│ ├── scope.py ← ScopeController (VXI-11, DSO80204B)
│ ├── psu.py ← PSUController (VXI-11, SPD3303X-E)
│ └── target.py ← TargetController (HTTP REST)
├── analysis/
│ ├── __init__.py
│ ├── waveform.py ← D-PHY timing extraction from CSV
│ ├── registers.py ← SN65 / DSIM register parsing & flagging
│ └── report.py ← Per-run HTML/JSON/CSV report generation
├── server/
│ ├── app.py ← Flask REST server (deploy on i.MX8)
│ └── hw_interface.py ← memtool, i2cget, display/video control
├── master_loop.py ← Main entry point — the flicker hunt loop
└── captures/ ← Auto-created; one subfolder per run
└── run_001_20260505_143022/
├── waveform_ch1.csv
├── waveform_ch2.csv
├── waveform_ch3.csv
├── waveform_ch4.csv
├── registers.json
└── summary.txt
4. PYTHON DEPENDENCIES (requirements.txt)
python-vxi11>=0.9
requests>=2.28
flask>=3.0 # server side only
numpy>=1.24
pandas>=2.0
matplotlib>=3.7 # optional, for waveform plots
5. CONFIGURATION (config.py)
# Network
TARGET_IP = "192.168.45.8"
TARGET_PORT = 5000
SCOPE_IP = "192.168.45.4"
PSU_IP = "192.168.45.3"
# Scope hardware
SCOPE_CHANNELS = {
"CLK_P": 1, # MIPI Clock Lane +
"CLK_N": 2, # MIPI Clock Lane -
"DAT0_P": 3, # MIPI Data Lane 0 +
"DAT0_N": 4, # MIPI Data Lane 0 -
}
PROBE_ATTENUATION = 19.2 # 910R + 50R divider, calibrated
SCOPE_TIMEBASE = 5e-9 # 5 ns/div — resolves ~430 MHz transitions
SCOPE_POINTS = 500_000
TRIGGER_CHANNEL = 3 # DAT0+ — catches LP-01→LP-00 SoT entry
TRIGGER_LEVEL_V = 0.05 # 50 mV after 19.2× attenuation factor
# (represents ~960 mV on actual signal)
TRIGGER_SLOPE = "NEGative"
# PSU
PSU_CHANNEL_DISPLAY = 1
PSU_DISPLAY_VOLTAGE = 3.3
PSU_DISPLAY_CURRENT = 1.0
PSU_POWER_CYCLE_DELAY_S = 2.0
# Pixel clock & DSI parameters
PIXEL_CLOCK_HZ = 72_000_000
DSI_LANES = 4
BITS_PER_PIXEL = 24
# DSI clock = pixel_clock * bpp / lanes (DDR, so /2 for freq)
DSI_CLK_HZ = PIXEL_CLOCK_HZ * BITS_PER_PIXEL // DSI_LANES # 432 MHz
BYTE_CLK_HZ = DSI_CLK_HZ // 8 # 54 MHz
UI_NS = 1e9 / DSI_CLK_HZ # ~2.315 ns
# MIPI D-PHY v1.1 timing minimums (nanoseconds)
# Table 14, Section 9 — all are MINIMUM values; violations cause SoT errors
DPHY_SPEC = {
"t_lpx": 50.0,
"t_clk_prepare": 38.0,
"t_clk_zero": 262.0, # clk_prepare + clk_zero >= 300 ns
"t_clk_prepare_plus_zero": 300.0, # combined hard limit
"t_clk_trail": 60.0,
"t_clk_post": 60.0 + 52 * UI_NS,
"t_hs_prepare": 40.0 + 4 * UI_NS,
"t_hs_zero": 145.0 + 10 * UI_NS,
"t_hs_trail": max(8 * UI_NS, 60.0 + 4 * UI_NS),
"t_hs_exit": 100.0,
}
# SN65DSI83 I2C
SN65_I2C_ADDR = 0x2C
SN65_I2C_BUS = 2 # /dev/i2c-2 on target
SN65_REG_IRQ = 0xE5 # IRQ_STAT — volatile, must bypass cache
SN65_REG_PLL = 0x0A # RC_LVDS_PLL — bit 7 = PLL lock
SN65_REG_CLK = 0x0B # RC_LVDS_CLK — bit 0 = clock detect
# Error bit masks in SN65 REG_IRQ (0xE5)
SN65_ERR_SYNCH = (1 << 3) # CHA_SYNCH_ERR
SN65_ERR_SOT = (1 << 4) # CHA_SOT_ERR
SN65_ERR_UNC = (1 << 6) # CHA_UNC_ECC_ERR
SN65_FLICKER_MASK = SN65_ERR_SYNCH | SN65_ERR_SOT | SN65_ERR_UNC
# DSIM PHY timing registers (i.MX8M Mini, samsung-dsim)
DSIM_PHYTIMING_BASE = 0x32E100B4 # PHY_TIMING offset 0
DSIM_PHYTIMING1 = 0x32E100B8 # PHY_TIMING1 offset 4
DSIM_PHYTIMING2 = 0x32E100BC # PHY_TIMING2 offset 8
# Tweak parameters available via u-boot env (for reference/logging)
TWEAK_BIT_FIFO_FLUSH = (1 << 0)
TWEAK_BIT_ROUND_UP = (1 << 2)
# Output
CAPTURE_ROOT = "captures"
6. MODULE SPECIFICATIONS
6.1 hardware/psu.py — PSUController
Purpose: Control the Siglent SPD3303X-E to power-cycle the display 3.3 V rail.
Library: python-vxi11
SCPI command reference:
*IDN? → identity string
CH1:VOLTage <V> → set voltage
CH1:CURRent <A> → set current limit
OUTPut CH1,ON / OFF → enable/disable output
MEASure:VOLTage? CH1 → read back actual voltage
MEASure:CURRent? CH1 → read back actual current
Methods required:
| Method | Signature | Description |
|---|---|---|
__init__ |
(ip: str) |
Connect via vxi11, verify IDN, configure CH1 to 3.3 V / 1.0 A |
output_on |
() |
OUTPut CH1,ON |
output_off |
() |
OUTPut CH1,OFF |
power_cycle |
(delay_s=2.0) |
Off → sleep → On |
measure |
() → dict |
Returns {"voltage_v": float, "current_a": float} |
close |
() |
Disconnect instrument |
6.2 hardware/scope.py — ScopeController
Purpose: Configure the DSO80204B, arm triggers, detect acquisitions, and download waveform data.
Library: python-vxi11
Key SCPI commands for DSO80204B:
*RST → factory reset
:RUN / :STOP / :SINGle → run modes
:CHANnel<N>:DISPlay ON
:CHANnel<N>:INPut DC50 → 50Ω termination (REQUIRED)
:CHANnel<N>:PROBe 19.2 → attenuation ratio
:CHANnel<N>:SCALe 0.05 → 50 mV/div
:CHANnel<N>:OFFSet 0.0
:CHANnel<N>:LABel '<name>'
:TIMebase:SCALe 5E-9 → 5 ns/div
:TIMebase:POSition 0
:TIMebase:REFerence CENTer
:TRIGger:MODE EDGE
:TRIGger:EDGE:SOURce CHANnel<N>
:TRIGger:EDGE:SLOPe NEGative
:TRIGger:EDGE:LEVel <V>
:TRIGger:SWEep NORMal
:ACQuire:MODE RTIMe
:ACQuire:INTerpolate ON
:ACQuire:POINts 500000
:DISPlay:LAYout STACKed
:TER? → trigger event register (1 = triggered)
:WAVeform:SOURce CHANnel<N>
:WAVeform:FORMat ASCii
:WAVeform:STReaming ON
:WAVeform:DATA? → returns CSV waveform data
:WAVeform:PREamble? → returns x-increment, x-origin etc.
Methods required:
| Method | Signature | Description |
|---|---|---|
__init__ |
(ip: str) |
Connect, verify IDN |
setup |
() |
Full channel + timebase + trigger configuration per config.py |
arm_single |
() |
:SINGle — waits for one trigger event then holds |
wait_for_trigger |
(timeout_s=30) → bool |
Poll :TER? every 100 ms; return True on trigger |
download_waveform |
(channel: int) → pd.DataFrame |
Fetch ASCII waveform, apply preamble scaling, return DataFrame with columns time_s, voltage_v |
download_all |
() → dict[str, pd.DataFrame] |
Download all 4 channels; keys: CLK_P, CLK_N, DAT0_P, DAT0_N |
close |
() |
Disconnect |
Important: After downloading a waveform, apply the x_increment and x_origin from :WAVeform:PREamble? so timestamps are absolute seconds.
6.3 hardware/target.py — TargetController
Purpose: HTTP REST client for the i.MX 8M Mini target.
Library: requests
Endpoints (all at http://192.168.45.8:5000):
| Method | Endpoint | Payload | Returns |
|---|---|---|---|
| GET | /registers |
— | JSON: DSIM PHY_TIMING registers |
| GET | /sn65_registers |
— | JSON: Full SN65DSI83 register map |
| GET | /sn65_settling |
— | JSON: Register poll over settling window |
| PUT | /display |
{"state": "on"|"off"} |
{"ok": true} |
| PUT | /video |
{"action": "start"|"stop", "mode": "static-pink"} |
{"ok": true} |
Methods required:
| Method | Signature | Description |
|---|---|---|
__init__ |
(ip: str, port: int) |
Build base URL, verify connectivity with a GET /registers |
get_dsim_registers |
() → dict |
GET /registers |
get_sn65_registers |
() → dict |
GET /sn65_registers — server MUST bypass regmap cache before reading |
get_sn65_settling |
() → dict |
GET /sn65_settling |
display_on |
() |
PUT /display {"state": "on"} |
display_off |
() |
PUT /display {"state": "off"} |
video_start |
(mode="static-pink") |
PUT /video {"action": "start", "mode": mode} |
video_stop |
() |
PUT /video {"action": "stop"} |
7. REST SERVER SPECIFICATION (server/app.py)
Deploy on i.MX 8M Mini. Python 3.x, Flask.
7.1 GET /registers
Executes on the SoM:
memtool md -l 0x32e100b4+0x0c
Parse and return:
{
"PHY_TIMING": "0x00000305",
"PHY_TIMING1": "0x020e0a03",
"PHY_TIMING2": "0x00030605",
"raw_hex": "00000305 020e0a03 00030605"
}
7.2 GET /sn65_registers
Critical: Before any I2C read, execute:
echo 1 > /sys/kernel/debug/regmap/2-002c/cache_bypass
Then read the SN65DSI83 register file:
cat /sys/kernel/debug/regmap/2-002c/registers
Parse the output and return a JSON dict of { "reg_hex": value_hex } pairs.
Always explicitly include and flag these registers:
{
"registers": { "00": "35", ..., "0a": "85", "e5": "00" },
"pll_locked": true, // reg 0x0A bit 7
"clk_detected": true, // reg 0x0B bit 0
"irq_stat_raw": "0x00", // reg 0xE5 raw value
"sot_err": false, // reg 0xE5 bit 4
"synch_err": false, // reg 0xE5 bit 3
"unc_ecc_err":false // reg 0xE5 bit 6
}
7.3 GET /sn65_settling
Poll GET /sn65_registers every 100 ms for 2 seconds after power-up.
Return array of snapshots with timestamps. This catches transient error spikes
during the LP→HS initialization handshake.
7.4 PUT /display
{"state": "on"} → echo 0 > /sys/class/graphics/fb0/blank
{"state": "off"} → echo 4 > /sys/class/graphics/fb0/blank
7.5 PUT /video
{"action": "start", "mode": "static-pink"}
Start a GStreamer pipeline (or equivalent) that outputs a solid pink framebuffer image to drive continuous DSI traffic. Example pipeline:
gst-launch-1.0 videotestsrc pattern=solid-color foreground-color=0xFFFF69B4 \
! video/x-raw,width=1280,height=800,framerate=60/1 \
! fbdevsink device=/dev/fb0
{"action": "stop"} → kill the pipeline.
8. ANALYSIS MODULE SPECIFICATIONS
8.1 analysis/waveform.py
Input: Dict of DataFrames from ScopeController.download_all()
Purpose: Extract D-PHY timing parameters from raw voltage waveforms.
8.1.1 Signal Reconstruction
- Compute differential signals:
CLK_DIFF = CLK_P - CLK_N,DAT0_DIFF = DAT0_P - DAT0_N - Compute common-mode:
CLK_CM = (CLK_P + CLK_N) / 2
8.1.2 Lane State Detection
The D-PHY lane state machine uses single-ended voltage levels for LP states and differential for HS. Thresholds (after 19.2× probe):
| State | CLK_P | CLK_N | Interpretation |
|---|---|---|---|
| LP-11 (Stop/Idle) | ~62 mV | ~62 mV | Both high (~1.2 V on wire) |
| LP-01 (HS Request) | ~0 mV | ~62 mV | Dp low, Dn high |
| LP-00 (Bridge) | ~0 mV | ~0 mV | Both low (Prepare phase) |
| HS-0 (HS burst) | diff ~±10 mV | — | Low-swing differential |
Threshold for LP "high": > 40 mV post-attenuation (~770 mV on wire). Threshold for LP "low": < 10 mV post-attenuation (~192 mV on wire).
8.1.3 Timing Measurements Required
measure_t_lpx(data_lane_p, data_lane_n) → float (ns)
- Measure duration of LP-01 state on data lane (Dp low, Dn high).
measure_t_hs_prepare(data_lane_p, data_lane_n) → float (ns)
- Measure duration of LP-00 state on data lane before HS-0 transition.
measure_t_clk_prepare(clk_p, clk_n) → float (ns)
- Measure duration of LP-00 on clock lane before HS clock starts.
measure_t_clk_zero(clk_p, clk_n) → float (ns)
- Measure duration of HS-0 (differential zero) on clock lane before first clock toggle. Start: exit LP-00. End: first differential edge.
measure_t_clk_prepare_plus_zero(clk_p, clk_n) → float (ns)
- Combined
t_clk_prepare + t_clk_zero. Must be >= 300 ns.
measure_t_hs_zero(data_lane_p, data_lane_n) → float (ns)
- Measure HS-0 preamble duration on data lane before SoT sync byte (00011101).
8.1.4 Spec Validation
check_spec_compliance(measurements: dict) → dict
- Compare each measured value against
DPHY_SPECfromconfig.py. - Return dict with per-parameter:
{"measured_ns": float, "min_ns": float, "pass": bool, "margin_ns": float}
8.2 analysis/registers.py
parse_sn65(reg_json: dict) → dict
- Extract and interpret all flags from
/sn65_registersresponse. - Return structured dict including
flicker_detected: boolbased onSN65_FLICKER_MASK.
parse_dsim(reg_json: dict) → dict
- Decode PHY_TIMING, PHY_TIMING1, PHY_TIMING2 into individual cycle counts.
- PHY_TIMING (0x32E100B4): bits [7:4]=lpx, bits[3:0]=hs_exit
- PHY_TIMING1 (0x32E100B8): bits[31:24]=clk_zero, bits[23:16]=clk_post, bits[15:8]=clk_trail, bits[7:0]=clk_prepare
- PHY_TIMING2 (0x32E100BC): bits[23:16]=hs_prepare, bits[15:8]=hs_zero, bits[7:0]=hs_trail
- Convert cycles to nanoseconds:
ns = cycles / BYTE_CLK_HZ * 1e9
8.3 analysis/report.py
Per-run outputs saved to captures/run_NNN_YYYYMMDD_HHMMSS/:
| File | Format | Content |
|---|---|---|
waveform_ch1.csv through ch4.csv |
CSV | Raw scope data, columns: time_s, voltage_v |
registers.json |
JSON | DSIM + SN65 register snapshot at trigger time |
timing_analysis.json |
JSON | Measured D-PHY timings + spec compliance results |
summary.txt |
Text | Human-readable pass/fail + key measurements |
flicker_log.csv |
CSV (appended) | Master log — one row per run |
flicker_log.csv columns:
run_id, timestamp, flicker_detected, sot_err, synch_err, pll_locked,
t_lpx_ns, t_hs_prepare_ns, t_hs_prepare_pass,
t_clk_prepare_ns, t_clk_zero_ns, t_clk_prep_plus_zero_ns, t_clk_prep_zero_pass,
phy_timing_raw, phy_timing1_raw, phy_timing2_raw,
notes
9. MASTER LOOP (master_loop.py)
9.1 Startup Sequence
1. Instantiate PSUController, ScopeController, TargetController
2. PSU: Configure CH1 to 3.3V / 1.0A, output OFF
3. Target: PUT /display off, PUT /video stop
4. Scope: setup() — full channel + trigger configuration
5. Log start time and initial DSIM register state
9.2 Main Loop (runs until KeyboardInterrupt or max_runs reached)
FOR each run:
[RESET PHASE]
1. target.display_off()
2. target.video_stop()
3. psu.output_off()
4. sleep(PSU_POWER_CYCLE_DELAY_S)
[ARM PHASE]
5. scope.arm_single()
6. Create timestamped run directory under captures/
[STIMULUS PHASE]
7. psu.output_on()
8. sleep(0.5) # allow rails to stabilise
9. target.display_on()
10. target.video_start(mode="static-pink")
[ACQUIRE PHASE]
11. triggered = scope.wait_for_trigger(timeout_s=30)
12. If NOT triggered: log "TIMEOUT", continue to next run
[CAPTURE PHASE]
13. waveforms = scope.download_all() # all 4 channels
14. sn65_data = target.get_sn65_registers()
15. dsim_data = target.get_dsim_registers()
16. settling = target.get_sn65_settling()
[ANALYSIS PHASE]
17. timings = waveform.measure_all(waveforms)
18. spec_pass = waveform.check_spec_compliance(timings)
19. sn65_parsed = registers.parse_sn65(sn65_data)
20. dsim_parsed = registers.parse_dsim(dsim_data)
[DETECT PHASE]
21. flicker_detected = sn65_parsed["flicker_detected"]
22. If flicker_detected: print "*** FLICKER EVENT CAPTURED ***"
[SAVE PHASE]
23. Save all waveform CSVs
24. Save registers.json, timing_analysis.json, summary.txt
25. Append row to flicker_log.csv
[REPORT PHASE]
26. Print one-line status: run ID, flicker Y/N, key timing margins
END LOOP
9.3 Command-Line Arguments
python master_loop.py [options]
--max-runs N Stop after N captures (default: unlimited)
--timeout S Scope trigger timeout per run in seconds (default: 30)
--pixel-clock HZ Override pixel clock (default: 72000000)
--note "string" Append a note to every log row (e.g. "dsi-tweak=5")
--no-video Skip PUT /video (test display blank/unblank only)
--output-dir PATH Override captures root directory
10. DSIM REGISTER DECODE REFERENCE
At 72 MHz pixel clock (DSI = 432 MHz, byte clock = 54 MHz):
Expected "Round Up" values (dsi-tweak bit 2 set):
PHY_TIMING = 0x00000306
[7:4] hs_exit = 0x0 = 0 cycles ← NOTE: check kernel log for actual value
[3:0] lpx = 6 = 6 cycles → 111 ns (spec ≥50 ns ✓)
PHY_TIMING1 = 0x03120a04
[31:24] clk_zero = 0x03 = 3? ← cross-check with kernel log
[23:16] clk_post = 0x12 = 18 cycles
[15:8] clk_trail = 0x0a = 10 cycles
[7:0] clk_prepare = 0x04 = 4 cycles → 74 ns (spec ≥38 ns ✓)
PHY_TIMING2 = 0x00040707
[23:16] hs_prepare = 0x04 = 4 cycles → 74 ns
[15:8] hs_zero = 0x07 = 7 cycles → 130 ns
[7:0] hs_trail = 0x07 = 7 cycles → 130 ns
Note: The exact bit-field layout is not well-documented in the i.MX8M Mini reference manual (see the TODO comment in
samsung-dsim.c). The register parser should log the raw hex values AND the decoded cycle counts so they can be cross-referenced against kernel log output for verification.
11. SN65DSI83 REGISTER REFERENCE
I2C address: 0x2C (ADDR pin = GND)
Bus: i2c-2 on target (/dev/i2c-2)
| Register | Address | Key Bits | Description |
|---|---|---|---|
| RC_RESET | 0x09 | [0] = SOFT_RESET | Write 1 to reset |
| RC_LVDS_PLL | 0x0A | [7] = PLL_EN, read-back = PLL_LOCK | Volatile — reads hardware |
| RC_LVDS_CLK | 0x0B | [0] = CLK_DETECT | Clock detected on DSI input |
| IRQ_STAT | 0xE5 | [6]=UNC_ECC, [4]=SOT_ERR, [3]=SYNCH_ERR, [1]=CRC_ERR | Volatile — must bypass cache |
Cache bypass (execute before reading IRQ_STAT):
echo 1 > /sys/kernel/debug/regmap/2-002c/cache_bypass
Manual I2C read (fallback if regmap unavailable):
i2cget -y -f 2 0x2c 0xe5
Test pattern commands (useful for isolating LVDS vs DSI fault):
# Enable test pattern (removes DSI as variable)
i2cset -y -f 4 0x2c 0x3c 0x10
# Disable test pattern
i2cset -y -f 4 0x2c 0x3c 0x00
12. U-BOOT TUNING PARAMETERS (Reference)
These are set on the SoM via U-Boot environment variables. The REST server
can optionally expose a PUT /uboot_env endpoint to change them between
test runs (requires reboot), or they can be set manually between test sessions.
# Pixel clock
setenv flb_dtovar/lvds-freq 72000000
# DSI clock override (should be >= 6 × lvds-freq for 24bpp/4-lane)
setenv flb_dtovar/dsi-freq 432000000
# DSI tweak bitmask
# Bit 0: FIFO flush on VSync (recommended ON)
# Bit 2: Round-up rounding (recommended ON)
setenv flb_dtovar/dsi-tweak 5
# Per-parameter extra cycle padding
setenv flb_dtovar/dsi-phy-extra-clk_zero 3
setenv flb_dtovar/dsi-phy-extra-hs_prepare 1
setenv flb_dtovar/dsi-phy-extra-hs_trail 1
# Other available padding parameters
# flb_dtovar/dsi-phy-extra-lpx
# flb_dtovar/dsi-phy-extra-hs_exit
# flb_dtovar/dsi-phy-extra-clk_prepare
# flb_dtovar/dsi-phy-extra-clk_post
# flb_dtovar/dsi-phy-extra-clk_trail
# flb_dtovar/dsi-phy-extra-hs_zero
saveenv
reset
Note: Changing
lvds-freqbreaks U-Boot splash (hardcoded PLL table).dsi-tweak/ padding params do NOT require matching splash changes.
13. PACKET-LEVEL DECODE (Lane 0 Data Analysis)
This section defines how the analysis script should decode the DSI packet stream from the raw Lane 0 differential waveform. This is the ground truth fault detector, validated by the Falcon board manual decode (S. Bouriot, May 2024).
13.1 DSI Long Packet Structure (24-bit RGB, Data Type 0x3E)
1 byte 2 bytes 1 byte WORD COUNT bytes 2 bytes
[DATA TYPE][WORD COUNT][ ECC ][ 24bpp Pixel Stream ... ][ CRC ]
<-------- Packet Header ------> <-- Packet Payload ----> Footer
With 4 data lanes, bytes are distributed across lanes in round-robin order. Lane 0 carries bytes 0, 4, 8, 12... of the serialised packet stream.
For a 1024-pixel-wide line (24bpp, 4 lanes):
- Total payload bytes = 1024 x 3 = 3072
- Bytes on Lane 0 = 768
- Theoretical clock edges per line (header excluded) = 1024 x 3 x 8 / 4 = 6144 (Falcon measured ~6150 OK / ~6146 KO — within measurement error, line count consistent)
13.2 Short Packet Types Observed
0x21= Sync Event, H Sync Start (short packet preceding each long packet)0x3E= Packed Pixel Stream, 24-bit RGB (long packet, one per display line)- Each frame is preceded by a group of short packets (~39 LP11->LP0x transitions per frame, per Falcon measurement on a 768-line display)
13.3 Healthy Frame Signature
Lane 0 byte sequence after SoT:
0x21 → H-Sync short packet (repeated ~39 times as frame preamble)
0x3E → Long packet Data Type (start of line 1 RGB payload)
[payload byte 0]: non-zero R/G/B value present IMMEDIATELY from start of payload
Example with test pixels (R=0xD1, G=0xB5, B=0x90):
Lane 0 payload bytes: D1 B5 90 00 D1 B5 90 00 ... (pixel 0 RR, pixel 1 GG, pixel 2 BB...)
13.4 Fault Signature A — Pixel Data Offset (Shifted Display)
First long packet after frame preamble:
0x3E → Data Type correct
payload bytes 0..N: 00 00 00 00 00 00 ... ← ALL ZEROS, pixel data absent
Second long packet (~995 µs later per Falcon measurement):
payload at offset ~15 µs: D1 B5 90 ← pixel data arrives in WRONG packet/position
This is confirmed by Falcon analysis as the direct cause of visible horizontal shift. The packet headers are identical in OK and KO states — the fault is purely in payload position, not in framing or Data Type bytes.
13.5 Fault Signature B — Lane Stall (Flickering/Blackout)
- Lane 0 differential collapses to ~0 V (LP-11 Stop state) for ~20 ms at ~20 ms intervals
- Zero HS bursts transmitted during stall window
- Abnormal — never observed in healthy display operation
- Corresponds to the Nexio "total blackout" failure mode requiring power cycle to recover
- May co-occur with Fault A (shifted display + flicker seen simultaneously on Falcon)
13.6 Packet Decode Implementation Notes
analysis/waveform.py should implement decode_lane0_packets():
- Compute
DAT0_DIFF = DAT0_P - DAT0_N - Identify HS burst boundaries: look for differential swing exceeding ±8 mV (post 19.2x attenuation) after a period of LP-00 (both lines near 0 V)
- Use recovered clock from
CLK_DIFFedges to sample data bits (DDR — both edges) - Deserialise bytes MSB-first; check for Lane 0 SoT sync word (
0xB8after theLP-11 -> LP-01 -> LP-00 -> HS-0preamble) - Extract Data Type byte, Word Count (2 bytes), ECC (1 byte), then payload
- Return structured list:
[{burst_idx, timestamp_s, data_type, word_count, payload_hex}]
Scope window constraint: At 5 ns/div with 500 kpts, the capture window is ~2.5 µs — sufficient for SoT + packet header + first ~200 bytes of payload. For full line decode, set timebase to 500 ns/div or use the scope's segmented memory mode if available. The header check alone (first 4 payload bytes = 0x00 vs non-zero) is sufficient to classify Fault A without needing to decode the entire line.
14. KNOWN FAILURE MODES & EXPECTED SIGNATURES
14.1 Transient Flicker (Vertical Jitter)
- Frequency: Every few seconds to minutes depending on pixel clock
- Scope signature: Normal-looking SoT sequence but
t_hs_prepareort_clk_prepare + t_clk_zerojust below spec minimum - Register signature: REG_IRQ 0xE5 bits 3 or 4 set transiently
- Packet signature: First long packet payload starts with 0x00 bytes; pixel data found offset into second packet (confirmed by Falcon analysis)
- Recovery: Automatic — next valid frame corrects sync
14.2 Total Display Blackout
- Frequency: Rare; requires power cycle or driver unbind/rebind to recover
- Cause: DSI controller enters hang state; display power rail remains up (shared with touchscreen, light sensor, LED)
- Recovery steps:
echo 3-0023 > /sys/bus/i2c/devices/i2c-3/3-0023/driver/unbind echo 3-002d > /sys/bus/i2c/devices/i2c-3/3-002d/driver/unbind echo 3-0041 > /sys/bus/i2c/devices/i2c-3/3-0041/driver/unbind echo 4 > /sys/class/graphics/fb0/blank echo 0 > /sys/class/graphics/fb0/blank - Scope signature: LP lines fail to return from LP-00 to LP-11 after burst; lane stays in LP-11 for ~20 ms intervals (confirmed by Falcon analysis)
14.3 Pixel Clock Sensitivity Pattern
Empirically observed — some clock frequencies are stable, others cause consistent jitter. The boundary appears non-monotonic:
| Pixel Clock | Behaviour |
|---|---|
| 70 MHz | Stable |
| 71 MHz | Jitter |
| 72 MHz | Stable (current setting) |
| 72.4 MHz | Jitter (datasheet "typical") |
| 73 MHz | Stable |
| 74 MHz | Jitter |
| 75–76 MHz | Stable |
This non-monotonic pattern suggests resonance interaction between the DSI PLL
and the SN65DSI83 PLL, or undocumented constraints in the samsung-dsim PHY.
The scope capture at multiple clock frequencies may reveal whether t_clk_prepare + t_clk_zero tracks the instability boundary.
15. IMPORTANT IMPLEMENTATION NOTES
-
Always use
DC 50Ωinput on scope — 1 MΩ input with MIPI-speed signals will produce useless ringing. The 910R + 50R divider is only correct with 50Ω input. -
19.2× attenuation: The probe factor is
(910 + 50) / 50 = 19.2. LP-11 state (~1.2 V on wire) appears as ~62.5 mV at scope. HS swing (~200 mV on wire) appears as ~10.4 mV. Set scope vertical scale to 50 mV/div. -
Trigger level 50 mV (post-attenuation): This catches the falling edge of DAT0+ from LP-01 (~62 mV) to LP-00 (~0 mV) — the entry into T_HS_PREPARE.
-
Cache bypass is mandatory before reading IRQ_STAT (0xE5). Without it you get the last-written value, not the current hardware state. The server must do this every time
/sn65_registersis called. -
Unit Interval (UI): At 72 MHz pixel clock, DSI = 432 Mbps per lane, UI = 1/432MHz ≈ 2.315 ns. All spec minimums involving UI must be calculated dynamically if pixel clock is changed via
--pixel-clockargument. -
The DSIM register layout is undocumented (see TODO in
samsung-dsim.c). Log raw hex AND decoded values. Cross-reference against kerneldmesgoutput which prints the cycle counts explicitly whendsi-tweaklogging is enabled.