feat: expand Intel GPU display management

This commit is contained in:
daniel committed 2026-07-30 17:12:08 +01:00
1 parent 86de3400a9
commit 471903bafb
25 files changed
+1715 -16

No files matched your search

+595
View File
@@ -61,6 +61,29 @@ namespace Drivers::Graphics::IntelGPU {
static volatile uint64_t g_vblankCount = 0;
static bool g_vblankIrqReady = false;
// Modesetting/display-management state. The firmware-trained port and
// link are deliberately retained; the driver owns the active transcoder,
// timing catalogue, panel PWM, and validated timing transactions.
static constexpr int MaxDisplayModes = 32;
struct ModeRecord {
DisplayMode timing;
uint32_t refreshMilliHz;
uint32_t flags;
};
static ModeRecord g_modes[MaxDisplayModes] = {};
static int g_modeCount = 0;
static int g_currentMode = -1;
static uint32_t g_transTimingBase = TRANS_TIMING_A;
static uint32_t g_transConfReg = TRANS_CONF_A;
static uint32_t g_transDdiReg = TRANS_DDI_A;
static uint8_t g_connectorType = montauk::abi::DISPLAY_CONNECTOR_UNKNOWN;
static bool g_connectorConnected = false;
static bool g_edidValid = false;
static bool g_pwmSupported = false;
static uint32_t g_pwmCtlReg = 0;
static char g_connectorName[32] = "Unknown";
static char g_monitorName[64] = "Unknown display";
// =========================================================================
// Register access helpers
// =========================================================================
@@ -73,6 +96,33 @@ namespace Drivers::Graphics::IntelGPU {
return *(volatile uint32_t*)(g_mmioBase + reg);
}
static void CopyText(char* dst, uint64_t cap, const char* src) {
if (!dst || cap == 0) return;
uint64_t i = 0;
if (src) {
while (i + 1 < cap && src[i]) {
dst[i] = src[i];
i++;
}
}
dst[i] = '\0';
}
static void CopyEdidText(char* dst, uint64_t cap, const uint8_t* src, int len) {
if (!dst || cap == 0) return;
int end = len;
while (end > 0 && (src[end - 1] == ' ' || src[end - 1] == '\n'
|| src[end - 1] == '\r' || src[end - 1] == '\0')) {
end--;
}
uint64_t out = 0;
for (int i = 0; i < end && out + 1 < cap; i++) {
uint8_t ch = src[i];
dst[out++] = (ch >= 0x20 && ch <= 0x7e) ? (char)ch : '?';
}
dst[out] = '\0';
}
// =========================================================================
// PCI Detection
// =========================================================================
@@ -317,6 +367,350 @@ namespace Drivers::Graphics::IntelGPU {
return true;
}
// =========================================================================
// Connector discovery, EDID, and mode catalogue
// =========================================================================
static uint32_t ModeRefreshMilliHz(const DisplayMode& mode) {
uint64_t pixels = (uint64_t)mode.htotal * mode.vtotal;
if (pixels == 0 || mode.pixelClock == 0) return 0;
return (uint32_t)(((uint64_t)mode.pixelClock * 1000000ULL + pixels / 2) / pixels);
}
static bool SameMode(const ModeRecord& a, const DisplayMode& mode,
uint32_t refreshMilliHz) {
if (a.timing.hdisplay != mode.hdisplay || a.timing.vdisplay != mode.vdisplay)
return false;
if (a.timing.htotal && mode.htotal
&& (a.timing.htotal != mode.htotal || a.timing.vtotal != mode.vtotal))
return false;
uint32_t ar = a.refreshMilliHz;
uint32_t diff = ar > refreshMilliHz ? ar - refreshMilliHz : refreshMilliHz - ar;
return ar == 0 || refreshMilliHz == 0 || diff <= 500;
}
static int AddMode(const DisplayMode& mode, uint32_t refreshMilliHz,
uint32_t flags) {
if (mode.hdisplay < 320 || mode.vdisplay < 200
|| mode.hdisplay > 16384 || mode.vdisplay > 16384)
return -1;
if (refreshMilliHz == 0) refreshMilliHz = ModeRefreshMilliHz(mode);
for (int i = 0; i < g_modeCount; i++) {
if (SameMode(g_modes[i], mode, refreshMilliHz)) {
g_modes[i].flags |= flags;
if (g_modes[i].timing.pixelClock == 0 && mode.pixelClock)
g_modes[i].timing = mode;
return i;
}
}
if (g_modeCount >= MaxDisplayModes) return -1;
int index = g_modeCount++;
g_modes[index].timing = mode;
g_modes[index].refreshMilliHz = refreshMilliHz;
g_modes[index].flags = flags;
return index;
}
static void AddSimpleMode(uint32_t width, uint32_t height, uint32_t refreshHz,
uint32_t flags = 0) {
DisplayMode mode = {};
mode.hdisplay = width;
mode.vdisplay = height;
AddMode(mode, refreshHz * 1000, flags);
}
static bool EdidBlockValid(const uint8_t* block) {
uint8_t sum = 0;
for (int i = 0; i < 128; i++) sum = (uint8_t)(sum + block[i]);
return sum == 0;
}
static bool ParseDetailedTiming(const uint8_t* dtd, DisplayMode& mode) {
uint32_t clock10KHz = (uint32_t)dtd[0] | ((uint32_t)dtd[1] << 8);
if (clock10KHz == 0) return false;
uint32_t hactive = dtd[2] | ((uint32_t)(dtd[4] & 0xF0) << 4);
uint32_t hblank = dtd[3] | ((uint32_t)(dtd[4] & 0x0F) << 8);
uint32_t vactive = dtd[5] | ((uint32_t)(dtd[7] & 0xF0) << 4);
uint32_t vblank = dtd[6] | ((uint32_t)(dtd[7] & 0x0F) << 8);
uint32_t hsyncOffset = dtd[8] | ((uint32_t)(dtd[11] & 0xC0) << 2);
uint32_t hsyncWidth = dtd[9] | ((uint32_t)(dtd[11] & 0x30) << 4);
uint32_t vsyncOffset = ((dtd[10] >> 4) & 0x0F)
| ((uint32_t)(dtd[11] & 0x0C) << 2);
uint32_t vsyncWidth = (dtd[10] & 0x0F)
| ((uint32_t)(dtd[11] & 0x03) << 4);
if (!hactive || !vactive || !hblank || !vblank) return false;
mode = {};
mode.hdisplay = hactive;
mode.hsyncStart = hactive + hsyncOffset;
mode.hsyncEnd = mode.hsyncStart + hsyncWidth;
mode.htotal = hactive + hblank;
mode.vdisplay = vactive;
mode.vsyncStart = vactive + vsyncOffset;
mode.vsyncEnd = mode.vsyncStart + vsyncWidth;
mode.vtotal = vactive + vblank;
mode.pixelClock = clock10KHz * 10;
mode.hsyncPositive = (dtd[17] & (1u << 1)) != 0;
mode.vsyncPositive = (dtd[17] & (1u << 2)) != 0;
return true;
}
static void ParseEdid() {
uint64_t edidSize = 0;
const uint8_t* edid = ::Graphics::Framebuffer::GetEdid(&edidSize);
if (!edid || edidSize < 128) return;
static constexpr uint8_t header[8] = {0x00, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00};
if (memcmp(edid, header, sizeof(header)) != 0 || !EdidBlockValid(edid)) {
KernelLogStream(WARNING, "IntelGPU") << "Boot EDID failed validation";
return;
}
g_edidValid = true;
// Established timings from EDID 1.3 section 3.8.
if (edid[35] & 0x20) AddSimpleMode(640, 480, 60);
if (edid[35] & 0x08) AddSimpleMode(640, 480, 72);
if (edid[35] & 0x04) AddSimpleMode(640, 480, 75);
if (edid[35] & 0x02) AddSimpleMode(800, 600, 56);
if (edid[35] & 0x01) AddSimpleMode(800, 600, 60);
if (edid[36] & 0x80) AddSimpleMode(800, 600, 72);
if (edid[36] & 0x40) AddSimpleMode(800, 600, 75);
if (edid[36] & 0x08) AddSimpleMode(1024, 768, 60);
if (edid[36] & 0x04) AddSimpleMode(1024, 768, 70);
if (edid[36] & 0x02) AddSimpleMode(1024, 768, 75);
if (edid[36] & 0x01) AddSimpleMode(1280, 1024, 75);
// Standard timings carry geometry and refresh but no porch/sync data.
for (int off = 38; off < 54; off += 2) {
if (edid[off] == 0x01 && edid[off + 1] == 0x01) continue;
uint32_t width = ((uint32_t)edid[off] + 31) * 8;
uint32_t aspect = (edid[off + 1] >> 6) & 3;
uint32_t height = aspect == 0 ? width * 10 / 16
: aspect == 1 ? width * 3 / 4
: aspect == 2 ? width * 4 / 5
: width * 9 / 16;
AddSimpleMode(width, height, (edid[off + 1] & 0x3F) + 60);
}
// Base-block detailed timings and monitor name descriptor.
bool firstDetailed = true;
for (int off = 54; off + 18 <= 126; off += 18) {
DisplayMode mode;
if (ParseDetailedTiming(edid + off, mode)) {
AddMode(mode, 0, firstDetailed ? montauk::abi::DISPLAY_MODE_PREFERRED : 0);
firstDetailed = false;
} else if (edid[off + 3] == 0xFC) {
CopyEdidText(g_monitorName, sizeof(g_monitorName), edid + off + 5, 13);
}
}
// CTA-861 extension detailed timings. Other extension types are kept
// untouched; their mode encodings need format-specific parsers.
uint32_t blocks = 1 + edid[126];
if (blocks * 128 > edidSize) blocks = (uint32_t)(edidSize / 128);
for (uint32_t block = 1; block < blocks; block++) {
const uint8_t* ext = edid + block * 128;
if (!EdidBlockValid(ext) || ext[0] != 0x02) continue;
int start = ext[2];
if (start == 0 || start < 4 || start > 126) continue;
for (int off = start; off + 18 <= 127; off += 18) {
DisplayMode mode;
if (ParseDetailedTiming(ext + off, mode)) AddMode(mode, 0, 0);
}
}
KernelLogStream(OK, "IntelGPU") << "EDID: " << g_monitorName
<< ", " << base::dec << (uint64_t)g_modeCount << " modes";
}
struct TranscoderCandidate {
uint32_t timing;
uint32_t conf;
uint32_t ddi;
const char* name;
bool edp;
};
static void DiscoverActiveTranscoder() {
static constexpr TranscoderCandidate candidates[] = {
{TRANS_TIMING_EDP, TRANS_CONF_EDP, TRANS_DDI_EDP, "eDP-1", true},
{TRANS_TIMING_A, TRANS_CONF_A, TRANS_DDI_A, "Pipe A", false},
{TRANS_TIMING_B, TRANS_CONF_B, TRANS_DDI_B, "Pipe B", false},
{TRANS_TIMING_C, TRANS_CONF_C, TRANS_DDI_C, "Pipe C", false},
};
int selected = -1;
for (int pass = 0; pass < 2 && selected < 0; pass++) {
for (int i = 0; i < (int)(sizeof(candidates) / sizeof(candidates[0])); i++) {
uint32_t conf = ReadReg(candidates[i].conf);
uint32_t ddi = ReadReg(candidates[i].ddi);
if (!(conf & PIPECONF_ENABLE)) continue;
if (pass == 0 && !(ddi & TRANS_DDI_ENABLE)) continue;
selected = i;
break;
}
}
if (selected < 0) selected = 1; // conservative legacy Pipe A fallback
const auto& trans = candidates[selected];
g_transTimingBase = trans.timing;
g_transConfReg = trans.conf;
g_transDdiReg = trans.ddi;
g_connectorConnected = (ReadReg(trans.conf) & PIPECONF_ENABLE) != 0;
uint32_t ddi = ReadReg(trans.ddi);
if (g_gpuGen <= 7) {
if (ReadReg(LVDS) & LVDS_PORT_ENABLE) {
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_LVDS;
CopyText(g_connectorName, sizeof(g_connectorName), "LVDS-1");
} else if (ReadReg(ADPA) & ADPA_DAC_ENABLE) {
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_VGA;
CopyText(g_connectorName, sizeof(g_connectorName), "VGA-1");
} else if ((ReadReg(DP_B) | ReadReg(DP_C) | ReadReg(DP_D)) & (1u << 31)) {
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_DP;
CopyText(g_connectorName, sizeof(g_connectorName), "DP-1");
} else {
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_HDMI;
CopyText(g_connectorName, sizeof(g_connectorName), "HDMI-1");
}
} else if (trans.edp) {
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_EDP;
CopyText(g_connectorName, sizeof(g_connectorName), "eDP-1");
} else {
uint32_t mode = (ddi & TRANS_DDI_MODE_MASK) >> 24;
uint32_t port = (ddi & TRANS_DDI_PORT_MASK) >> 27;
const char* suffix = port == 0 ? "A" : port == 1 ? "B"
: port == 2 ? "C" : port == 3 ? "D"
: port == 8 ? "TC1" : port == 9 ? "TC2"
: port == 10 ? "TC3" : port == 11 ? "TC4"
: "unknown";
if (mode == 0) {
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_HDMI;
CopyText(g_connectorName, sizeof(g_connectorName), "HDMI-");
} else if (mode == 1) {
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_DVI;
CopyText(g_connectorName, sizeof(g_connectorName), "DVI-");
} else {
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_DP;
CopyText(g_connectorName, sizeof(g_connectorName), "DP-");
}
uint64_t len = 0;
while (len + 1 < sizeof(g_connectorName) && g_connectorName[len]) len++;
CopyText(g_connectorName + len, sizeof(g_connectorName) - len, suffix);
}
KernelLogStream(OK, "IntelGPU") << "Active transcoder " << trans.name
<< ", connector " << g_connectorName
<< (g_connectorConnected ? " connected" : " disconnected");
}
static DisplayMode CaptureActiveMode() {
DisplayMode mode = {};
uint32_t htotal = ReadReg(g_transTimingBase + 0x00);
uint32_t hsync = ReadReg(g_transTimingBase + 0x08);
uint32_t vtotal = ReadReg(g_transTimingBase + 0x0C);
uint32_t vsync = ReadReg(g_transTimingBase + 0x14);
mode.hdisplay = (htotal & 0xFFFF) + 1;
mode.htotal = ((htotal >> 16) & 0xFFFF) + 1;
mode.hsyncStart = (hsync & 0xFFFF) + 1;
mode.hsyncEnd = ((hsync >> 16) & 0xFFFF) + 1;
mode.vdisplay = (vtotal & 0xFFFF) + 1;
mode.vtotal = ((vtotal >> 16) & 0xFFFF) + 1;
mode.vsyncStart = (vsync & 0xFFFF) + 1;
mode.vsyncEnd = ((vsync >> 16) & 0xFFFF) + 1;
return mode;
}
static void SelectCurrentMode() {
DisplayMode active = CaptureActiveMode();
if (active.hdisplay == 0 || active.hdisplay > 16384
|| active.vdisplay == 0 || active.vdisplay > 16384) {
active.hdisplay = (uint32_t)g_fbWidth;
active.vdisplay = (uint32_t)g_fbHeight;
}
// Match the register totals against a detailed EDID timing to recover
// the firmware-selected pixel clock exactly.
for (int i = 0; i < g_modeCount; i++) {
const auto& timing = g_modes[i].timing;
if (timing.hdisplay == active.hdisplay && timing.vdisplay == active.vdisplay
&& timing.htotal == active.htotal && timing.vtotal == active.vtotal
&& timing.pixelClock != 0) {
active.pixelClock = timing.pixelClock;
active.hsyncPositive = timing.hsyncPositive;
active.vsyncPositive = timing.vsyncPositive;
g_currentMode = i;
break;
}
}
uint32_t refresh = ModeRefreshMilliHz(active);
if (refresh == 0) refresh = 60000;
if (g_currentMode < 0) g_currentMode = AddMode(active, refresh, 0);
if (g_currentMode < 0 && g_modeCount == MaxDisplayModes) {
// A pathological EDID can fill the catalogue before the firmware
// mode is considered. The active mode is never optional: retain
// it by replacing the final non-preferred catalogue entry.
int replacement = MaxDisplayModes - 1;
for (int i = MaxDisplayModes - 1; i >= 0; i--) {
if (!(g_modes[i].flags & montauk::abi::DISPLAY_MODE_PREFERRED)) {
replacement = i;
break;
}
}
g_modes[replacement].timing = active;
g_modes[replacement].refreshMilliHz = refresh;
g_modes[replacement].flags = 0;
g_currentMode = replacement;
}
if (g_currentMode < 0) return;
g_modes[g_currentMode].flags |= montauk::abi::DISPLAY_MODE_CURRENT
| montauk::abi::DISPLAY_MODE_DRIVER_VALID;
uint32_t activeClock = g_modes[g_currentMode].timing.pixelClock;
for (int i = 0; i < g_modeCount; i++) {
const auto& mode = g_modes[i].timing;
if (mode.hdisplay == active.hdisplay && mode.vdisplay == active.vdisplay
&& mode.pixelClock != 0 && activeClock != 0
&& mode.pixelClock == activeClock) {
g_modes[i].flags |= montauk::abi::DISPLAY_MODE_DRIVER_VALID;
}
}
}
static void DiscoverPwm() {
g_pwmSupported = false;
if (g_connectorType != montauk::abi::DISPLAY_CONNECTOR_EDP
&& g_connectorType != montauk::abi::DISPLAY_CONNECTOR_LVDS)
return;
uint32_t pch = ReadReg(BLC_PWM_PCH_CTL2);
if ((pch >> 16) != 0) {
g_pwmCtlReg = BLC_PWM_PCH_CTL2;
g_pwmSupported = true;
} else {
uint32_t cpu = ReadReg(BLC_PWM_CPU_CTL);
if ((cpu >> 16) != 0) {
g_pwmCtlReg = BLC_PWM_CPU_CTL;
g_pwmSupported = true;
}
}
KernelLogStream(INFO, "IntelGPU") << "Panel PWM "
<< (g_pwmSupported ? "available" : "not exposed");
}
static void InitializeDisplayManagement() {
g_modeCount = 0;
g_currentMode = -1;
g_edidValid = false;
CopyText(g_monitorName, sizeof(g_monitorName), "Unknown display");
DiscoverActiveTranscoder();
ParseEdid();
SelectCurrentMode();
DiscoverPwm();
}
// =========================================================================
// GTT Initialization
// =========================================================================
@@ -850,6 +1244,7 @@ namespace Drivers::Graphics::IntelGPU {
KernelLogStream(ERROR, "IntelGPU") << "Failed to read display state";
return false;
}
InitializeDisplayManagement();
if (!InitializeGtt()) {
KernelLogStream(ERROR, "IntelGPU") << "Failed to initialize GTT";
@@ -910,6 +1305,7 @@ namespace Drivers::Graphics::IntelGPU {
KernelLogStream(ERROR, "IntelGPU") << "Failed to read display state";
return;
}
InitializeDisplayManagement();
// Step 5: Initialize GTT
if (!InitializeGtt()) {
@@ -976,6 +1372,205 @@ namespace Drivers::Graphics::IntelGPU {
return g_fbPitch;
}
// =========================================================================
// Display management public API
// =========================================================================
static bool WaitTranscoderState(bool enabled) {
for (int i = 0; i < 200000; i++) {
bool state = (ReadReg(g_transConfReg) & PIPECONF_STATE) != 0;
if (state == enabled) return true;
asm volatile("pause");
}
return false;
}
struct TimingSnapshot {
uint32_t htotal, hblank, hsync;
uint32_t vtotal, vblank, vsync;
};
static TimingSnapshot ReadTimingSnapshot() {
return {
ReadReg(g_transTimingBase + 0x00),
ReadReg(g_transTimingBase + 0x04),
ReadReg(g_transTimingBase + 0x08),
ReadReg(g_transTimingBase + 0x0C),
ReadReg(g_transTimingBase + 0x10),
ReadReg(g_transTimingBase + 0x14),
};
}
static void WriteTimingSnapshot(const TimingSnapshot& timing) {
WriteReg(g_transTimingBase + 0x00, timing.htotal);
WriteReg(g_transTimingBase + 0x04, timing.hblank);
WriteReg(g_transTimingBase + 0x08, timing.hsync);
WriteReg(g_transTimingBase + 0x0C, timing.vtotal);
WriteReg(g_transTimingBase + 0x10, timing.vblank);
WriteReg(g_transTimingBase + 0x14, timing.vsync);
}
static uint32_t PackTiming(uint32_t start, uint32_t end) {
return ((end - 1) << 16) | (start - 1);
}
static bool ApplyModeTiming(const DisplayMode& mode) {
if (!g_connectorConnected || mode.hdisplay == 0 || mode.vdisplay == 0
|| mode.htotal <= mode.hdisplay || mode.vtotal <= mode.vdisplay
|| mode.hsyncStart < mode.hdisplay || mode.hsyncEnd <= mode.hsyncStart
|| mode.hsyncEnd > mode.htotal || mode.vsyncStart < mode.vdisplay
|| mode.vsyncEnd <= mode.vsyncStart || mode.vsyncEnd > mode.vtotal)
return false;
TimingSnapshot oldTiming = ReadTimingSnapshot();
uint32_t oldConf = ReadReg(g_transConfReg);
uint32_t oldPlane = ReadReg(DSPACNTR);
uint64_t oldSurface = (g_flipSupported && g_frontBuffer == 1)
? g_buf1GttOffset
: (g_flipSupported ? g_fbGttOffsetA : g_fbGttOffset);
// A timing-only modeset retains the firmware-trained DDI, PLL and link.
// Disable fetch and the transcoder, program all timing registers as one
// transaction, then restore them in the inverse order.
if (oldPlane & DISP_ENABLE) {
WriteReg(DSPACNTR, oldPlane & ~DISP_ENABLE);
WriteReg(DSPASURF, (uint32_t)oldSurface);
(void)ReadReg(DSPASURF);
}
WriteReg(g_transConfReg, oldConf & ~PIPECONF_ENABLE);
if (!WaitTranscoderState(false)) {
WriteReg(DSPACNTR, oldPlane);
WriteReg(DSPASURF, (uint32_t)oldSurface);
return false;
}
TimingSnapshot next = {
PackTiming(mode.hdisplay, mode.htotal),
PackTiming(mode.hdisplay, mode.htotal),
PackTiming(mode.hsyncStart, mode.hsyncEnd),
PackTiming(mode.vdisplay, mode.vtotal),
PackTiming(mode.vdisplay, mode.vtotal),
PackTiming(mode.vsyncStart, mode.vsyncEnd),
};
WriteTimingSnapshot(next);
WriteReg(g_transConfReg, oldConf | PIPECONF_ENABLE);
if (!WaitTranscoderState(true)) {
// Roll back while the transcoder is stopped. A failed timing must
// never leave the panel black merely because userspace requested it.
WriteReg(g_transConfReg, oldConf & ~PIPECONF_ENABLE);
(void)WaitTranscoderState(false);
WriteTimingSnapshot(oldTiming);
WriteReg(g_transConfReg, oldConf);
(void)WaitTranscoderState((oldConf & PIPECONF_ENABLE) != 0);
WriteReg(DSPACNTR, oldPlane);
WriteReg(DSPASURF, (uint32_t)oldSurface);
(void)ReadReg(DSPASURF);
return false;
}
WriteReg(DSPACNTR, oldPlane);
WriteReg(DSPASURF, (uint32_t)oldSurface);
(void)ReadReg(DSPASURF);
return true;
}
bool GetDisplayInfo(montauk::abi::DisplayInfo* out) {
if (!out) return false;
memset(out, 0, sizeof(*out));
out->brightness = -1;
out->currentMode = -1;
if (!g_initialized) return false;
out->capabilities = montauk::abi::DISPLAY_CAP_MODESET;
if (g_edidValid) out->capabilities |= montauk::abi::DISPLAY_CAP_EDID;
if (g_pwmSupported) out->capabilities |= montauk::abi::DISPLAY_CAP_BRIGHTNESS;
if (g_flipSupported) out->capabilities |= montauk::abi::DISPLAY_CAP_PAGE_FLIP;
if (g_vblankIrqReady) out->capabilities |= montauk::abi::DISPLAY_CAP_VBLANK_IRQ;
for (int i = 0; i < g_modeCount; i++) {
if (i != g_currentMode
&& (g_modes[i].flags & montauk::abi::DISPLAY_MODE_DRIVER_VALID)) {
out->capabilities |= montauk::abi::DISPLAY_CAP_SAFE_SWITCH;
break;
}
}
out->modeCount = (uint32_t)g_modeCount;
out->currentMode = g_currentMode;
out->brightness = Brightness(-1);
out->deviceId = g_gpuInfo.deviceId;
out->generation = g_gpuInfo.gen;
out->connectorType = g_connectorType;
out->connected = g_connectorConnected ? 1 : 0;
CopyText(out->gpuName, sizeof(out->gpuName),
g_gpuInfo.name ? g_gpuInfo.name : "Intel GPU");
CopyText(out->connectorName, sizeof(out->connectorName), g_connectorName);
CopyText(out->monitorName, sizeof(out->monitorName), g_monitorName);
return true;
}
int GetDisplayModes(montauk::abi::DisplayModeInfo* out, int maxCount) {
if (!g_initialized || maxCount < 0) return -1;
int count = g_modeCount < maxCount ? g_modeCount : maxCount;
if (count > 0 && !out) return -1;
for (int i = 0; i < count; i++) {
out[i].width = g_modes[i].timing.hdisplay;
out[i].height = g_modes[i].timing.vdisplay;
out[i].refreshMilliHz = g_modes[i].refreshMilliHz;
out[i].pixelClockKHz = g_modes[i].timing.pixelClock;
out[i].flags = g_modes[i].flags;
out[i]._pad = 0;
}
return count;
}
int SetDisplayMode(int modeIndex) {
if (!g_initialized || modeIndex < 0 || modeIndex >= g_modeCount) return -1;
if (g_currentMode < 0 || g_currentMode >= g_modeCount) return -1;
if (!(g_modes[modeIndex].flags & montauk::abi::DISPLAY_MODE_DRIVER_VALID))
return -2;
if (modeIndex == g_currentMode) return modeIndex;
// Link rate and framebuffer geometry remain fixed. Only timings with
// the same active area and pixel clock are safe without retraining the
// firmware-established HDMI/DP/eDP link.
const DisplayMode& current = g_modes[g_currentMode].timing;
const DisplayMode& requested = g_modes[modeIndex].timing;
if (requested.hdisplay != current.hdisplay
|| requested.vdisplay != current.vdisplay
|| requested.pixelClock == 0
|| requested.pixelClock != current.pixelClock)
return -2;
if (!ApplyModeTiming(requested)) {
KernelLogStream(ERROR, "IntelGPU") << "Modeset transaction failed; previous mode restored";
return -3;
}
g_modes[g_currentMode].flags &= ~montauk::abi::DISPLAY_MODE_CURRENT;
g_currentMode = modeIndex;
g_modes[g_currentMode].flags |= montauk::abi::DISPLAY_MODE_CURRENT;
KernelLogStream(OK, "IntelGPU") << "Mode set to " << base::dec
<< (uint64_t)requested.hdisplay << "x" << (uint64_t)requested.vdisplay
<< " @ " << (uint64_t)(g_modes[modeIndex].refreshMilliHz / 1000) << "Hz";
return modeIndex;
}
int Brightness(int percent) {
if (!g_initialized || !g_pwmSupported) return -1;
uint32_t value = ReadReg(g_pwmCtlReg);
uint32_t maximum = value >> 16;
if (maximum == 0) return -1;
if (percent < 0) {
uint32_t duty = value & 0xFFFF;
if (duty > maximum) duty = maximum;
return (int)(((uint64_t)duty * 100 + maximum / 2) / maximum);
}
if (percent > 100) percent = 100;
uint32_t duty = (uint32_t)(((uint64_t)maximum * (uint32_t)percent + 50) / 100);
WriteReg(g_pwmCtlReg, (value & 0xFFFF0000u) | duty);
(void)ReadReg(g_pwmCtlReg);
return Brightness(-1);
}
// =========================================================================
// Page flip public API
// =========================================================================