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

16 KiB
Raw Blame History

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

// 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)

// 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)

// 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

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

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

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

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

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

// 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

// 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

// 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

// 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

// 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

// 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)

// 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+)

// 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):

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):

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):

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):

// __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)

// __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