Files
RedBear-OS/local/docs/INTEL-DISPLAY-SUBSYSTEM-REFERENCE.md
T
vasilito 98326148ef 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/.
2026-05-30 12:52:11 +03:00

450 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 | Gen24 | 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): 10200ms → write value = `delay_ms * 10`
- Power down delay (T10): 0500ms → 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(&gt->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 |
|---|---|---|
| Gen23 | 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 |