From 98326148ef4db89c95d2e176557bc68cca8bed2a Mon Sep 17 00:00:00 2001 From: Admin Pupkin Date: Sat, 30 May 2026 12:52:11 +0300 Subject: [PATCH] Add Intel display subsystem reference: backlight, PPS, hangcheck, reset Extracted from local/reference/linux-7.1/drivers/gpu/drm/i915/: - Panel backlight: BLC_PWM_CTL/CTL2 register layouts, PWM frequency formulas for all platforms (Gen2 through BXT/CNP), enable/disable sequences - Panel power sequencing: PP_STATUS/PP_CONTROL/PP_*_DELAYS/PP_DIVISOR register offsets and bit layouts, power-on/off/VDD sequences, delay computation - GPU hang detection: ACTHD comparison, ring head/tail tracking, hangcheck state machine, timeout thresholds - GPU engine reset: GEN6_GDRST/GEN8_GDRST/RING_RESET_CTL register definitions, per-engine reset sequences for Gen8+, global reset flows, platform variations (Gen2 through MTL+) Intended as technical reference for Intel driver implementation in local/recipes/gpu/redox-drm/source/src/drivers/intel/. --- .../docs/INTEL-DISPLAY-SUBSYSTEM-REFERENCE.md | 449 ++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 local/docs/INTEL-DISPLAY-SUBSYSTEM-REFERENCE.md diff --git a/local/docs/INTEL-DISPLAY-SUBSYSTEM-REFERENCE.md b/local/docs/INTEL-DISPLAY-SUBSYSTEM-REFERENCE.md new file mode 100644 index 0000000000..36cee5cda1 --- /dev/null +++ b/local/docs/INTEL-DISPLAY-SUBSYSTEM-REFERENCE.md @@ -0,0 +1,449 @@ +# Intel Display Subsystem Reference — Linux i915 Patterns + +**Extracted:** 2026-05-30 +**Source:** `local/reference/linux-7.1/drivers/gpu/drm/i915/` +**Purpose:** Exact register layouts, bit definitions, and initialization sequences for four Intel display subsystems — extracted for Rust implementation in `local/recipes/gpu/redox-drm/source/src/drivers/intel/` + +--- + +## 1. PANEL BACKLIGHT CONTROL + +### Register Map (`intel_backlight_regs.h`) + +| Register | Offset | Platform | Purpose | +|---|---|---|---| +| `BLC_PWM_CTL` | 0x61254 | Gen2–4 | Legacy PWM duty cycle control | +| `BLC_PWM_CTL2` | 0x61250 | Gen4+ | Legacy PWM enable + pipe select | +| `BLC_PWM_CPU_CTL` | 0x48254 | ILK+ | CPU PWM duty cycle | +| `BLC_PWM_CPU_CTL2` | 0x48250 | ILK+ | CPU PWM enable | +| `BLC_PWM_PCH_CTL1` | 0xc8250 | PCH-split | PCH backlight enable/polarity | +| `BLC_PWM_PCH_CTL2` | 0xc8254 | PCH-split | PCH max duty cycle (upper 16 bits) | +| `BXT_BLC_PWM_CTL` | per-controller | BXT+ | PWM enable + polarity | +| `BXT_BLC_PWM_FREQ` | per-controller | BXT+ | PWM frequency divisor | +| `BXT_BLC_PWM_DUTY` | per-controller | BXT+ | PWM duty cycle | + +### Key Bit Definitions + +```c +// BLC_PWM_CTL2 (legacy gen4) +BLM_PWM_ENABLE = (1 << 31) +BLM_COMBINATION_MODE = (1 << 30) // gen4 only +BLM_PIPE_SELECT = (1 << 29) +BLM_PIPE(pipe) = ((pipe) << 29) +BLM_POLARITY_I965 = (1 << 28) + +// BLC_PWM_CTL (gen2-4) +BACKLIGHT_MODULATION_FREQ_SHIFT = 17 +BACKLIGHT_MODULATION_FREQ_MASK = (0x7fff << 17) +BACKLIGHT_DUTY_CYCLE_SHIFT = 0 +BACKLIGHT_DUTY_CYCLE_MASK = 0xffff +BACKLIGHT_DUTY_CYCLE_MASK_PNV = 0xfffe // Pineview 15-bit +BLM_LEGACY_MODE = (1 << 16) // gen2 only + +// BLC_PWM_PCH_CTL1 (PCH-split) +BLM_PCH_PWM_ENABLE = (1 << 31) +BLM_PCH_OVERRIDE_ENABLE = (1 << 30) // LPT+ override mode +BLM_PCH_POLARITY = (1 << 29) + +// BXT_BLC_PWM_CTL +BXT_BLC_PWM_ENABLE = (1 << 31) +BXT_BLC_PWM_POLARITY = (1 << 29) + +// UTIL_PIN_CTL (BXT controller 1 aux pin) +UTIL_PIN_ENABLE = (1 << 31) +UTIL_PIN_PIPE(x) = ((x) << 29) +UTIL_PIN_MODE_PWM = (1 << 24) +UTIL_PIN_POLARITY = (1 << 22) +``` + +### PWM Frequency Formulas by Platform + +| Platform | Clock | Formula | Notes | +|---|---|---|---| +| **Gen2** | HRAWCLK/32 | `clock / (freq * 32)` | | +| **Gen3 (Pineview)** | rawclk/32 | `clock / (freq * 32)` | | +| **Gen4** | CDCLK or rawclk | `clock / (freq * 128)` | G4X uses rawclk, otherwise CDCLK | +| **ILK/SNB/IVB** | rawclk * 128 | `rawclk / (freq * 128)` | | +| **VLV/CHV** | 25MHz/19.2MHz/rawclk | `clock / (freq * 16 or 128)` | Clock mux via CBR1_VLV | +| **LPT** | 135MHz (H) or 24MHz (LP) | `clock / (freq * 128 or 16)` | Alt increment via SOUTH_CHICKEN2 | +| **SPT** | 24MHz | `clock / (freq * 16 or 128)` | Alt increment via SOUTH_CHICKEN1 | +| **BXT/CNP** | 19.2MHz | `19200 / freq` | | +| **CNP** | rawclk | `rawclk / freq` | | + +### Enable Sequence (LPT and later PCH-split) + +```c +// lpt_enable_backlight() +1. Read existing BLC_PWM_PCH_CTL1; if BLM_PCH_PWM_ENABLE set, clear it first +2. For LPT: Set SOUTH_CHICKEN2 LPT_PWM_GRANULARITY based on panel->backlight.alternate_pwm_increment + For SPT: Set SOUTH_CHICKEN1 SPT_PWM_GRANULARITY +3. Write BLC_PWM_PCH_CTL2 = (pwm_level_max << 16) // Upper 16 bits = max duty +4. Build pch_ctl1 = 0 + - If active_low_pwm: pch_ctl1 |= BLM_PCH_POLARITY + - If LPT+: pch_ctl1 |= BLM_PCH_OVERRIDE_ENABLE +5. Write BLC_PWM_PCH_CTL1 = pch_ctl1 +6. Posting read BLC_PWM_PCH_CTL1 +7. Write BLC_PWM_PCH_CTL1 = pch_ctl1 | BLM_PCH_PWM_ENABLE +8. Call intel_backlight_set_pwm_level(conn_state, level) // Writes duty cycle to CTL2 +``` + +### Disable Sequence (LPT) + +```c +// lpt_disable_backlight() +1. intel_backlight_set_pwm_level(old_conn_state, level) // Set final level +2. If BLC_PWM_CPU_CTL2 has BLM_PWM_ENABLE: + Write BLC_PWM_CPU_CTL2 = tmp& ~BLM_PWM_ENABLE +3. Write BLC_PWM_PCH_CTL1 = BLM_PCH_PWM_ENABLE -> 0 +``` + +--- + +## 2. PANEL POWER SEQUENCING + +### Register Map (`intel_pps_regs.h`) + +| Register | Offset | Purpose | +|---|---|---| +| `PP_STATUS` | 0x61200 | Panel power status (read-only) | +| `PP_CONTROL` | 0x61204 | Panel power on/off + VDD force | +| `PP_ON_DELAYS` | 0x61208 | Power-up delay + light-on delay | +| `PP_OFF_DELAYS` | 0x6120C | Power-down delay + light-off delay | +| `PP_DIVISOR` | 0x61210 | Clock divider + power cycle delay | + +### PP_STATUS Bit Definitions + +```c +PP_ON = REG_BIT(31) +PP_READY = REG_BIT(30) +PP_SEQUENCE_MASK = REG_GENMASK(29, 28) +PP_SEQUENCE_NONE = 0 +PP_SEQUENCE_POWER_UP = 1 +PP_SEQUENCE_POWER_DOWN = 2 +PP_CYCLE_DELAY_ACTIVE = REG_BIT(27) +PP_SEQUENCE_STATE_MASK = REG_GENMASK(3, 0) +PP_SEQUENCE_STATE_OFF_IDLE = 0x0 +PP_SEQUENCE_STATE_ON_IDLE = 0x8 +PP_SEQUENCE_STATE_RESET = 0xf +``` + +### PP_CONTROL Bit Definitions + +```c +PANEL_UNLOCK_MASK = REG_GENMASK(31, 16) +PANEL_UNLOCK_REGS = 0xabcd +BXT_POWER_CYCLE_DELAY_MASK = REG_GENMASK(8, 4) // CNP+/BXT+ moved to PP_CONTROL +EDP_FORCE_VDD = REG_BIT(3) +EDP_BLC_ENABLE = REG_BIT(2) +PANEL_POWER_RESET = REG_BIT(1) +PANEL_POWER_ON = REG_BIT(0) +``` + +### PP_ON_DELAYS Bit Layout + +```c +PANEL_PORT_SELECT_MASK = REG_GENMASK(31, 30) +PANEL_PORT_SELECT_VLV(port) = REG_FIELD_PREP(PANEL_PORT_SELECT_MASK, port) +PANEL_POWER_UP_DELAY_MASK = REG_GENMASK(28, 16) // T1+T3 in 100µs units +PANEL_LIGHT_ON_DELAY_MASK = REG_GENMASK(12, 0) // T8 in 100µs units +``` + +### PP_OFF_DELAYS Bit Layout + +```c +PANEL_POWER_DOWN_DELAY_MASK = REG_GENMASK(28, 16) // T10 in 100µs units +PANEL_LIGHT_OFF_DELAY_MASK = REG_GENMASK(12, 0) // T9 in 100µs units +``` + +### PP_DIVISOR Bit Layout + +```c +PP_REFERENCE_DIVIDER_MASK = REG_GENMASK(31, 8) +PANEL_POWER_CYCLE_DELAY_MASK = REG_GENMASK(4, 0) // T11+T12 in 100ms units +``` + +### Power-On Sequence + +```c +// intel_pps_on_unlocked() +1. If panel already on, return early +2. wait_panel_power_cycle() // Ensure T12 delay elapsed +3. For ILK: temporarily disable PANEL_POWER_RESET +4. For DG2: disable SOUTH_DSPCLK_GATE_D PCH_DPLSUNIT_CLOCK_GATE_DISABLE +5. Set PANEL_POWER_ON + (if !ILK) PANEL_POWER_RESET +6. Write PP_CONTROL +7. Posting read PP_CONTROL +8. wait_panel_on() // Poll PP_STATUS until PP_ON + sequence idle +9. Record last_power_on = jiffies +10. Restore DPLS clock gate +11. For ILK: restore PANEL_POWER_RESET bit +``` + +### Power-Off Sequence + +```c +// intel_pps_off_unlocked() +1. Clear: PANEL_POWER_ON | PANEL_POWER_RESET | EDP_FORCE_VDD | EDP_BLC_ENABLE +2. Write PP_CONTROL +3. Posting read PP_CONTROL +4. wait_panel_off() // Poll until PP_ON == 0, sequence idle +5. Record panel_power_off_time = ktime_get_boottime() +``` + +### VDD Force Sequence + +```c +// intel_pps_vdd_on_unlocked() +1. If panel_power_off_time recorded, call wait_panel_power_cycle() if needed +2. Read PP_CONTROL, set EDP_FORCE_VDD +3. Write PP_CONTROL, posting read +4. If panel was not on, sleep panel_power_up_delay ms + +// intel_pps_vdd_off_sync_unlocked() +1. Read PP_CONTROL, clear EDP_FORCE_VDD +2. Write PP_CONTROL, posting read +3. If PANEL_POWER_ON was also cleared, record panel_power_off_time +``` + +### Delay Computation + +- All delays in PPS registers use **100µs units** +- Power up delay (T1+T3): 10–200ms → write value = `delay_ms * 10` +- Power down delay (T10): 0–500ms → write value = `delay_ms * 10` +- Power cycle delay (T11+T12): minimum 100ms granularity + - Written as `(value / 100ms) + 1` for PP_DIVISOR, or `+ 1` for BXT+ PP_CONTROL +- Final delay = `max(bios_delay, vbt_delay, spec_min)` +- power_cycle rounds up to nearest 100ms + +--- + +## 3. GPU HANG DETECTION + +### Hangcheck Timer Period + +From `intel_engine_heartbeat.c`: the hangcheck timer fires based on `I915_PARAM_HANGCHECK_PERIOD` (default 60 seconds). The actual check interval is 30 seconds (half the period). + +### ACTHD Register Comparison + +```c +// check_active_request() — each engine tracks its own ACTHD +// For RCS: ACTHD at RING_ACTHD(RENDER_RING_BASE) = 0xc874 +// For VCS: ACTHD at RING_ACTHD(GEN6_BSD_RING_BASE) = engine-specific +// For BCS: ACTHD at RING_ACTHD(BLT_RING_BASE) +// For VECS: ACTHD at RING_ACTHD(VEBOX_RING_BASE) + +// Check sequence: +// 1. Read active request's last known ACTHD from context +// 2. Read current ACTHD from hardware +// 3. If current > last by more than 2 DWORDS, engine is making progress +// 4. If equal for two consecutive checks, engine is hung +``` + +### Ring Head/Tail Comparison + +```c +// Per-engine ring state tracked via: +RING_HEAD(base) = base + 0x34 // HEAD_WRAP_COUNT | HEAD_ADDR +RING_TAIL(base) = base + 0x30 // TAIL_ADDR +RING_START(base) = base + 0x38 // Ring base address +RING_CTL(base) = base + 0x3c // Size and valid bits + +// Hang detection compares: +// - Ring head pointer vs breadcrumb seqno position +// - ACTHD vs last submitted request's tail +// - Request timeout vs jiffies + +// Timeout thresholds from i915_params: +// I915_DEFAULT_HANG_PERIOD = 120 seconds (guilty) +// I915_ACTIVE_HANG_PERIOD = 60 seconds (active request) +``` + +### Hangcheck State Machine + +```c +// Per-engine hang state: +I915_RESET_ENGINE + engine_id // Engine-specific reset in progress + +// Global reset state: +I915_RESET_BACKOFF // Another reset in progress +I915_WEDGED // GPU wedged, recovery impossible + +// Hangcheck comparison: +// 1. For each active request: compare elapsed time vs I915_ACTIVE_HANG_PERIOD +// 2. If exceeded: mark request as hung, increment gpu_error.reset_engine_count +// 3. If reset fails and retry >= 1, attempt global reset +// 4. Global reset failures set I915_WEDGED +``` + +--- + +## 4. GPU ENGINE RESET + +### Reset Register Definitions (`intel_gt_regs.h`, `intel_engine_regs.h`) + +```c +// Gen6+ global reset (PCI config space) +I915_GDRST = 0xc04 +GRDOM_RESET_ENABLE = (1 << 0) +GRDOM_RESET_STATUS = (1 << 1) +GRDOM_MEDIA = (1 << 2) +GRDOM_RENDER = (1 << 1) + +// Gen6+ engine-specific reset (MMIO) +GEN6_GDRST = 0x941c +GEN6_GRDOM_FULL = (1 << 0) +GEN6_GRDOM_RENDER = (1 << 1) +GEN6_GRDOM_MEDIA = (1 << 2) +GEN6_GRDOM_BLT = (1 << 3) +GEN6_GRDOM_VECS = (1 << 4) +GEN9_GRDOM_GUC = (1 << 5) +GEN8_GRDOM_MEDIA2 = (1 << 7) + +// Gen11+ expanded domains +GEN11_GRDOM_FULL = GEN6_GRDOM_FULL +GEN11_GRDOM_RENDER = GEN6_GRDOM_RENDER +GEN11_GRDOM_SFC0-3 = BIT(17-20) +GEN11_GRDOM_VECS4 = BIT(16) +// ... more media engines up to GEN11_GRDOM_MEDIA8 = BIT(12) + +// Per-engine ring reset control (engine-specific MMIO) +RING_RESET_CTL(base) = base + 0xd0 +RESET_CTL_CAT_ERROR = REG_BIT(2) // Catastrophic error, skip ready-to-reset +RESET_CTL_READY_TO_RESET = REG_BIT(1) // Engine ready for reset +RESET_CTL_REQUEST_RESET = REG_BIT(0) // Request reset +``` + +### Per-Engine Reset Sequence (Gen8+) + +```c +// gen8_engine_reset_prepare() +1. Read RING_RESET_CTL(engine->mmio_base) +2. If RESET_CTL_CAT_ERROR set: + request = RESET_CTL_CAT_ERROR + mask = RESET_CTL_CAT_ERROR + ack = 0 (HW clears cat error) + Else if RESET_CTL_READY_TO_RESET not set: + request = RESET_CTL_REQUEST_RESET + mask = RESET_CTL_READY_TO_RESET + ack = RESET_CTL_READY_TO_RESET + Else: + return 0 (already ready) +3. Write RING_RESET_CTL = REG_MASKED_FIELD_ENABLE(request) +4. Wait for RING_RESET_CTL & mask == ack, timeout 700µs +5. If timeout, log error and return -ETIMEDOUT + +// gen8_engine_reset_cancel() +1. Write RING_RESET_CTL = REG_MASKED_FIELD_DISABLE(RESET_CTL_REQUEST_RESET) +``` + +### Global Reset Sequences + +**Gen3 (PCI config):** +```c +pci_write_config_byte(pdev, I915_GDRST, GRDOM_RESET_ENABLE) +udelay(50) +_wait_for_atomic(i915_in_reset(pdev), 50000) +pci_write_config_byte(pdev, I915_GDRST, 0) +udelay(50) +_wait_for_atomic(!i915_in_reset(pdev), 50000) +``` + +**Gen4 (PCI config, separate media/render):** +```c +pci_write_config_byte(pdev, I915_GDRST, GRDOM_MEDIA | GRDOM_RESET_ENABLE) +_wait_for_atomic(g4x_reset_complete(), 50000) +pci_write_config_byte(pdev, I915_GDRST, GRDOM_RENDER | GRDOM_RESET_ENABLE) +_wait_for_atomic(g4x_reset_complete(), 50000) +pci_write_config_byte(pdev, I915_GDRST, 0) +``` + +**Gen6+ (MMIO domain reset):** +```c +loops = (GRAPHICS_VER_FULL < IP_VER(12, 70)) ? 2 : 1 // Jasperlake needs 2x +do { + intel_uncore_write_fw(uncore, GEN6_GDRST, hw_domain_mask) + err = __intel_wait_for_register_fw(uncore, GEN6_GDRST, + hw_domain_mask, 0, + 2000, 0, NULL) +} while (err == 0 && --loops) +udelay(50) // Settle delay after reset ack +``` + +**Gen11+ (with SFC lock handling):** +```c +// __gen11_reset_engines() +1. For each engine in mask: call gen11_lock_sfc() + - Read GEN11_VCS_SFC_FORCED_LOCK, set lock bit + - Wait 1000µs for GEN11_VCS_SFC_LOCK_ACK + - If lock obtained to different engine, add paired engine to unlock_mask +2. Call gen6_hw_domain_reset(gt, reset_mask) +3. For each engine in unlock_mask: call gen11_unlock_sfc() + - Clear GEN11_VCS_SFC_FORCED_LOCK_BIT +``` + +### Reset Flow (Top-Level) + +```c +// __intel_gt_reset() +reset = intel_get_gpu_reset(gt) +intel_uncore_forcewake_get(gt->uncore, FORCEWAKE_ALL) +for retry in 0..RESET_MAX_RETRIES: + reset_mask = wa_14015076503_start(gt, engine_mask, !retry) + ret = reset(gt, reset_mask, retry) + wa_14015076503_end(gt, reset_mask) + if ret == 0 break +intel_uncore_forcewake_put(gt->uncore, FORCEWAKE_ALL) +return ret + +// intel_gt_reset() +1. gt_revoke() // Revoke mmaps with userfault +2. mutex_lock(>->reset.mutex) +3. __intel_gt_unset_wedged() +4. reset_prepare() // Stop engines, prevent RC6 entry +5. do_reset() // Call __intel_gt_reset() +6. gt_reset() // Re-init GGTT, reset each engine, restore GuC +7. resume() // Resume each engine +8. reset_finish() // Signal breadcrumbs, put forcewake +``` + +### Platform Variations + +| Platform | Reset Mechanism | Notes | +|---|---|---| +| Gen2–3 | PCI config GDRST | Simple pulse reset | +| G33/G4x | PCI config GDRST | Separate media/render domains | +| ILK | ILK_GDSR MMIO | Separate render/media domains | +| Gen6+ | GEN6_GDRST MMIO | Per-domain reset bits | +| Gen8+ | gen8_reset_engines() | Per-engine RING_RESET_CTL sequence | +| Gen11+ | gen11_lock_sfc() + __gen11_reset_engines() | SFC forced lock before reset | +| DG2 | __gen11_reset_engines() first, then full reset | Wa_22011100796 | +| MTL+ | Wa_14015076503 for GSC engine | 200ms wait for GSC FW | + +--- + +## Implementation Notes for Red Bear Rust Driver + +1. **Backlight**: The PWM duty cycle register is the lower 16 bits of `BLC_PWM_CTL2` on legacy platforms. On PCH-split, the max duty goes in the upper 16 bits of `BLC_PWM_PCH_CTL2`. The enable bit is in `BLC_PWM_PCH_CTL1`. Track the platform variant and use the correct register pair. + +2. **PPS**: The power sequencing state machine relies on polling `PP_STATUS` bits with timeout. The sequence state machine in `PP_STATUS` tracks 16 states. VDD must be forced before powering on panel. T12 (power cycle delay) must be observed before re-powering. + +3. **Hangcheck**: The ACTHD comparison is between the hardware register and the last known position of the active request. If they match across two check intervals, the engine is hung. + +4. **Reset**: The `RING_RESET_CTL` sequence on Gen8+ requires waiting for `READY_TO_RESET` before issuing `REQUEST_RESET`. Catastrophic errors (`CAT_ERROR`) bypass the ready check. The reset settle delay (`udelay(50)`) is required before restoring engine state. + +--- + +## Key Source Files + +| File | Subsystem | Key content | +|---|---|---| +| `display/intel_backlight.c` | Backlight | PWM enable/disable sequences, level setting | +| `display/intel_backlight_regs.h` | Backlight | Register offsets + bit definitions | +| `display/intel_pps.c` | PPS | Power sequencing state machine, VDD control | +| `display/intel_pps_regs.h` | PPS | PP_STATUS/PP_CONTROL/PP_*_DELAYS/PP_DIVISOR | +| `gt/intel_reset.c` | Reset | Global + engine reset logic, RESET_MAX_RETRIES=3 | +| `gt/intel_engine_heartbeat.c` | Hangcheck | Heartbeat timer, hang detection | +| `gt/gen6_engine_cs.c` | Hangcheck | ACTHD comparison, ring state | +| `gt/intel_gt_regs.h` | Reset | GEN6_GDRST, GEN8_GDRST, domain bit definitions | +| `gt/intel_engine_regs.h` | Reset | RING_RESET_CTL, RING_HEAD/TAIL/CTL offsets | +| `gt/selftest_hangcheck.c` | Hangcheck | Hang injection + detection test patterns |