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/.
This commit is contained in:
2026-05-30 12:52:11 +03:00
parent af6d6ff607
commit 98326148ef
@@ -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 | 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 |