fix: add backlight modulation support for hybrid graphics laptops
This commit is contained in:
1 parent
7979acc2eb
commit
f9e21e0f13
6 files changed
+381
-12
No files matched your search
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* WmiEcBacklight.cpp
|
||||
* Firmware (EC) panel backlight behind the WMI interface NVIDIA specified
|
||||
* for display-mux laptops
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "WmiEcBacklight.hpp"
|
||||
#include "AcpiWmi.hpp"
|
||||
#include "AcpiPower.hpp"
|
||||
#include <ACPI/AML/AmlInterpreter.hpp>
|
||||
#include <CppLib/Spinlock.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Hal {
|
||||
namespace WmiEcBacklight {
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// Interface
|
||||
|
||||
// ============================================================================
|
||||
static constexpr const char* Guid = "603E9613-EF25-4338-A3D0-C46177516DB7";
|
||||
|
||||
// WMxx method IDs.
|
||||
static constexpr uint32_t MethodLevel = 1;
|
||||
static constexpr uint32_t MethodSource = 2;
|
||||
|
||||
// `mode` values in the argument block.
|
||||
static constexpr uint32_t ModeGet = 0;
|
||||
static constexpr uint32_t ModeSet = 1;
|
||||
static constexpr uint32_t ModeGetMaxLevel = 2;
|
||||
|
||||
static constexpr uint64_t SourceEc = 2; // 1 = GPU, 3 = eDP AUX
|
||||
|
||||
// Lowest level driven, so the panel never goes fully dark.
|
||||
static constexpr int MinPercent = 1;
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// State
|
||||
|
||||
// ============================================================================
|
||||
static kcp::Mutex g_lock;
|
||||
static bool g_probed = false;
|
||||
static bool g_available = false;
|
||||
static AcpiWmi::Block g_block{};
|
||||
static uint32_t g_maxLevel = 0;
|
||||
|
||||
// Argument block: { u32 mode; u32 val; u32 ret; u32 reserved[3]; }.
|
||||
// The method answers with an Integer. Linux reads it back as the
|
||||
// `ret` field of the same block, which is where ACPICA's copy-out of
|
||||
// an Integer puts the value; a firmware returning the block itself as
|
||||
// a Buffer is read at that same offset.
|
||||
static bool Call(uint32_t method, uint32_t mode, uint32_t value, uint32_t& out) {
|
||||
uint8_t args[24] = {};
|
||||
args[0] = (uint8_t)mode;
|
||||
args[1] = (uint8_t)(mode >> 8);
|
||||
args[2] = (uint8_t)(mode >> 16);
|
||||
args[3] = (uint8_t)(mode >> 24);
|
||||
args[4] = (uint8_t)value;
|
||||
args[5] = (uint8_t)(value >> 8);
|
||||
args[6] = (uint8_t)(value >> 16);
|
||||
args[7] = (uint8_t)(value >> 24);
|
||||
|
||||
AML::Object result{};
|
||||
if (!AcpiWmi::EvaluateMethod(g_block, 0, method, args, sizeof(args), result))
|
||||
return false;
|
||||
if (result.Type == AML::ObjectType::Integer) {
|
||||
out = (uint32_t)result.Integer;
|
||||
return true;
|
||||
}
|
||||
if (result.Type == AML::ObjectType::Buffer && result.Buffer.Length >= 12) {
|
||||
const uint8_t* r = result.Buffer.Data + 8;
|
||||
out = r[0] | (uint32_t)r[1] << 8 | (uint32_t)r[2] << 16 | (uint32_t)r[3] << 24;
|
||||
return true;
|
||||
}
|
||||
// A set may legitimately return nothing.
|
||||
out = 0;
|
||||
return mode == ModeSet;
|
||||
}
|
||||
|
||||
static void Probe() {
|
||||
g_probed = true;
|
||||
uint8_t guid[16];
|
||||
if (!AcpiWmi::ParseGuid(Guid, guid) || !AcpiWmi::Find(guid, g_block)) return;
|
||||
if (!(g_block.Flags & AcpiWmi::FlagMethod) || g_block.InstanceCount == 0) return;
|
||||
|
||||
char path[128];
|
||||
AML::GetInterpreter().GetNamespace().GetNodePath(g_block.Device, path, sizeof(path));
|
||||
|
||||
// The interface also exists on machines where the GPU drives the
|
||||
// backlight; there it must be left alone.
|
||||
uint32_t source = 0;
|
||||
if (!Call(MethodSource, ModeGet, 0, source)) {
|
||||
KernelLogStream(INFO, "Backlight") << "WMI EC backlight at " << path
|
||||
<< ": source query failed";
|
||||
return;
|
||||
}
|
||||
if (source != SourceEc) {
|
||||
KernelLogStream(INFO, "Backlight") << "WMI EC backlight at " << path
|
||||
<< " reports source " << base::dec << (uint64_t)source << ", not EC";
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t maximum = 0;
|
||||
if (!Call(MethodLevel, ModeGetMaxLevel, 0, maximum) || maximum == 0) {
|
||||
KernelLogStream(WARNING, "Backlight") << "WMI EC backlight at " << path
|
||||
<< ": no maximum level";
|
||||
return;
|
||||
}
|
||||
g_maxLevel = maximum;
|
||||
g_available = true;
|
||||
char method[5] = {'W', 'M', g_block.ObjectId[0], g_block.ObjectId[1], '\0'};
|
||||
KernelLogStream(OK, "Backlight") << "Panel brightness via EC (WMI "
|
||||
<< path << "." << method << ", " << base::dec << (uint64_t)maximum
|
||||
<< " levels)";
|
||||
}
|
||||
|
||||
static int ToPercent(uint32_t level) {
|
||||
if (level > g_maxLevel) level = g_maxLevel;
|
||||
return (int)(((uint64_t)level * 100 + g_maxLevel / 2) / g_maxLevel);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// Public API
|
||||
|
||||
// ============================================================================
|
||||
int Brightness(int percent) {
|
||||
g_lock.Acquire();
|
||||
if (!g_probed) Probe();
|
||||
int result = -1;
|
||||
if (g_available) {
|
||||
uint32_t level = 0;
|
||||
if (percent >= 0) {
|
||||
if (percent > 100) percent = 100;
|
||||
if (percent < MinPercent) percent = MinPercent;
|
||||
uint32_t target = (uint32_t)(((uint64_t)g_maxLevel * (uint32_t)percent + 50) / 100);
|
||||
if (target == 0) target = 1;
|
||||
uint32_t ignored = 0;
|
||||
if (!Call(MethodLevel, ModeSet, target, ignored)) {
|
||||
result = -2;
|
||||
} else {
|
||||
result = Call(MethodLevel, ModeGet, 0, level) ? ToPercent(level)
|
||||
: ToPercent(target);
|
||||
}
|
||||
} else {
|
||||
result = Call(MethodLevel, ModeGet, 0, level) ? ToPercent(level) : -2;
|
||||
}
|
||||
}
|
||||
g_lock.Release();
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
Reference in new issue
Block a user