161 lines
6.3 KiB
C++
161 lines
6.3 KiB
C++
/*
|
|
* 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;
|
|
}
|
|
|
|
};
|
|
};
|