mirror of
https://gitlab.freedesktop.org/upower/power-profiles-daemon.git
synced 2025-12-20 04:30:09 +01:00
Add support for a amdgpu panel power savings action
This activates the DRM connector attribute `panel_power_savings` which takes a range from 0 to 4 to indicate how aggresively to enable panel power savings. Link: https://lore.kernel.org/amd-gfx/0b94b25a-9ffa-41a5-b931-ad84e1892d36@amd.com/T/#m079d7c357626cf3a80cd9ba6239b3fe4fcf8937e
This commit is contained in:
parent
9a740c02d6
commit
41ed5d33a8
8 changed files with 414 additions and 0 deletions
|
|
@ -21,6 +21,7 @@ content_files += gnome.gdbus_codegen(
|
|||
private_headers = [
|
||||
'power-profiles-daemon.h',
|
||||
'ppd-action-trickle-charge.h',
|
||||
'ppd-action-amdgpu-panel-power.h',
|
||||
'ppd-driver-fake.h',
|
||||
'ppd-driver-intel-pstate.h',
|
||||
'ppd-driver-amd-pstate.h',
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@
|
|||
<xi:include href="xml/ppd-driver-cpu.xml"/>
|
||||
<xi:include href="xml/ppd-driver-platform.xml"/>
|
||||
<xi:include href="xml/ppd-action.xml"/>
|
||||
<xi:include href="xml/ppd-action-amdgpu-panel-power.xml"/>
|
||||
</reference>
|
||||
|
||||
<index>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,15 @@ PpdAction
|
|||
PPD_TYPE_ACTION
|
||||
</SECTION>
|
||||
|
||||
<SECTION>
|
||||
<FILE>ppd-action-amdgpu-panel-power</FILE>
|
||||
<TITLE>AMDGPU Power Panel Saving Action</TITLE>
|
||||
PpdActionAmdgpuPanelPowerClass
|
||||
_PpdActionAmdgpuPanelPower
|
||||
<SUBSECTION Private>
|
||||
PPD_TYPE_ACTION
|
||||
</SECTION>
|
||||
|
||||
<SECTION>
|
||||
<FILE>ppd-driver</FILE>
|
||||
<TITLE>Profile Drivers</TITLE>
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ libpower_profiles_daemon_dep = declare_dependency(
|
|||
sources += [
|
||||
'power-profiles-daemon.c',
|
||||
'ppd-action-trickle-charge.c',
|
||||
'ppd-action-amdgpu-panel-power.c',
|
||||
'ppd-driver-intel-pstate.c',
|
||||
'ppd-driver-amd-pstate.c',
|
||||
'ppd-driver-platform-profile.c',
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ static void start_profile_drivers (PpdApp *data);
|
|||
|
||||
/* profile drivers and actions */
|
||||
#include "ppd-action-trickle-charge.h"
|
||||
#include "ppd-action-amdgpu-panel-power.h"
|
||||
#include "ppd-driver-placeholder.h"
|
||||
#include "ppd-driver-platform-profile.h"
|
||||
#include "ppd-driver-intel-pstate.h"
|
||||
|
|
@ -109,6 +110,7 @@ static GTypeGetFunc objects[] = {
|
|||
|
||||
/* Actions */
|
||||
ppd_action_trickle_charge_get_type,
|
||||
ppd_action_amdgpu_panel_power_get_type,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
|
|
|
|||
329
src/ppd-action-amdgpu-panel-power.c
Normal file
329
src/ppd-action-amdgpu-panel-power.c
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Advanced Micro Devices
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 3 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "AmdgpuAction"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <gudev/gudev.h>
|
||||
|
||||
#include "ppd-action-amdgpu-panel-power.h"
|
||||
#include "ppd-profile.h"
|
||||
#include "ppd-utils.h"
|
||||
|
||||
#define PROC_CPUINFO_PATH "/proc/cpuinfo"
|
||||
|
||||
#define PANEL_POWER_SYSFS_NAME "amdgpu/panel_power_savings"
|
||||
|
||||
#define UPOWER_DBUS_NAME "org.freedesktop.UPower"
|
||||
#define UPOWER_DBUS_PATH "/org/freedesktop/UPower"
|
||||
#define UPOWER_DBUS_INTERFACE "org.freedesktop.UPower"
|
||||
|
||||
/**
|
||||
* SECTION:ppd-action-amdgpu-panel-power
|
||||
* @Short_description: Power savings for eDP connected displays
|
||||
* @Title: AMDGPU Panel power action
|
||||
*
|
||||
* The AMDGPU panel power action utilizes the sysfs attribute present on some DRM
|
||||
* connectors for amdgpu called "panel_power_savings". This will use an AMD specific
|
||||
* hardware feature for a power savings profile for the panel.
|
||||
*
|
||||
*/
|
||||
|
||||
struct _PpdActionAmdgpuPanelPower
|
||||
{
|
||||
PpdAction parent_instance;
|
||||
PpdProfile last_profile;
|
||||
|
||||
GUdevClient *client;
|
||||
GDBusProxy *proxy;
|
||||
guint watcher_id;
|
||||
|
||||
gint panel_power_saving;
|
||||
gboolean on_battery;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (PpdActionAmdgpuPanelPower, ppd_action_amdgpu_panel_power, PPD_TYPE_ACTION)
|
||||
|
||||
static GObject*
|
||||
ppd_action_amdgpu_panel_power_constructor (GType type,
|
||||
guint n_construct_params,
|
||||
GObjectConstructParam *construct_params)
|
||||
{
|
||||
GObject *object;
|
||||
|
||||
object = G_OBJECT_CLASS (ppd_action_amdgpu_panel_power_parent_class)->constructor (type,
|
||||
n_construct_params,
|
||||
construct_params);
|
||||
g_object_set (object,
|
||||
"action-name", "amdgpu_panel_power",
|
||||
NULL);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
set_panel_power (PpdActionAmdgpuPanelPower *self, gint power, GError **error)
|
||||
{
|
||||
GList *devices, *l;
|
||||
|
||||
devices = g_udev_client_query_by_subsystem (self->client, "drm");
|
||||
if (devices == NULL) {
|
||||
g_set_error_literal (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_NOT_FOUND,
|
||||
"no drm devices found");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
for (l = devices; l != NULL; l = l->next) {
|
||||
GUdevDevice *dev = l->data;
|
||||
const char *value;
|
||||
guint64 parsed;
|
||||
|
||||
value = g_udev_device_get_devtype (dev);
|
||||
if (g_strcmp0 (value, "drm_connector") != 0)
|
||||
continue;
|
||||
|
||||
value = g_udev_device_get_sysfs_attr_uncached (dev, PANEL_POWER_SYSFS_NAME);
|
||||
if (!value)
|
||||
continue;
|
||||
|
||||
parsed = g_ascii_strtoull (value, NULL, 10);
|
||||
|
||||
/* overflow check */
|
||||
if (parsed == G_MAXUINT64) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_DATA,
|
||||
"cannot parse %s as caused overflow",
|
||||
value);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (parsed == power)
|
||||
continue;
|
||||
|
||||
if (!ppd_utils_write_sysfs_int (dev, PANEL_POWER_SYSFS_NAME, power, error))
|
||||
return FALSE;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
g_list_free_full (devices, g_object_unref);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
ppd_action_amdgpu_panel_update_target (PpdActionAmdgpuPanelPower *self,
|
||||
GError **error)
|
||||
{
|
||||
gint target = 0;
|
||||
|
||||
/* only activate if we know that we're on battery */
|
||||
if (self->on_battery) {
|
||||
switch (self->last_profile) {
|
||||
case PPD_PROFILE_POWER_SAVER:
|
||||
target = 4;
|
||||
break;
|
||||
case PPD_PROFILE_BALANCED:
|
||||
target = 3;
|
||||
break;
|
||||
case PPD_PROFILE_PERFORMANCE:
|
||||
target = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!set_panel_power (self, target, error))
|
||||
return FALSE;
|
||||
self->panel_power_saving = target;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
ppd_action_amdgpu_panel_power_activate_profile (PpdAction *action,
|
||||
PpdProfile profile,
|
||||
GError **error)
|
||||
{
|
||||
PpdActionAmdgpuPanelPower *self = PPD_ACTION_AMDGPU_PANEL_POWER (action);
|
||||
self->last_profile = profile;
|
||||
|
||||
if (self->proxy == NULL) {
|
||||
g_debug ("upower not available; battery data might be stale");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return ppd_action_amdgpu_panel_update_target (self, error);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
upower_properties_changed (GDBusProxy *proxy,
|
||||
GVariant *changed_properties,
|
||||
GStrv invalidated_properties,
|
||||
PpdActionAmdgpuPanelPower *self)
|
||||
{
|
||||
g_autoptr (GVariant) battery_val = NULL;
|
||||
g_autoptr (GError) error = NULL;
|
||||
gboolean new_on_battery;
|
||||
|
||||
if (proxy != NULL)
|
||||
battery_val = g_dbus_proxy_get_cached_property (proxy, "OnBattery");
|
||||
new_on_battery = battery_val ? g_variant_get_boolean (battery_val) : FALSE;
|
||||
|
||||
if (self->on_battery == new_on_battery)
|
||||
return;
|
||||
|
||||
g_debug ("OnBattery: %d -> %d", self->on_battery, new_on_battery);
|
||||
self->on_battery = new_on_battery;
|
||||
if (!ppd_action_amdgpu_panel_update_target (self, &error))
|
||||
g_warning ("failed to update target: %s", error->message);
|
||||
}
|
||||
|
||||
static void
|
||||
udev_uevent_cb (GUdevClient *client,
|
||||
gchar *action,
|
||||
GUdevDevice *device,
|
||||
gpointer user_data)
|
||||
{
|
||||
PpdActionAmdgpuPanelPower *self = user_data;
|
||||
|
||||
if (!g_str_equal (action, "add"))
|
||||
return;
|
||||
|
||||
if (!g_udev_device_has_sysfs_attr (device, PANEL_POWER_SYSFS_NAME))
|
||||
return;
|
||||
|
||||
g_debug ("Updating panel power saving for '%s' to '%d'",
|
||||
g_udev_device_get_sysfs_path (device),
|
||||
self->panel_power_saving);
|
||||
ppd_utils_write_sysfs_int (device, PANEL_POWER_SYSFS_NAME,
|
||||
self->panel_power_saving, NULL);
|
||||
}
|
||||
|
||||
static PpdProbeResult
|
||||
ppd_action_amdgpu_panel_power_probe (PpdAction *action)
|
||||
{
|
||||
g_autofree gchar *cpuinfo_path = NULL;
|
||||
g_autofree gchar *cpuinfo = NULL;
|
||||
g_auto(GStrv) lines = NULL;
|
||||
|
||||
cpuinfo_path = ppd_utils_get_sysfs_path (PROC_CPUINFO_PATH);
|
||||
if (!g_file_get_contents (cpuinfo_path, &cpuinfo, NULL, NULL))
|
||||
return PPD_PROBE_RESULT_FAIL;
|
||||
|
||||
lines = g_strsplit (cpuinfo, "\n", -1);
|
||||
|
||||
for (gchar **line = lines; *line != NULL; line++) {
|
||||
if (g_str_has_prefix (*line, "vendor_id") &&
|
||||
strchr (*line, ':')) {
|
||||
g_auto(GStrv) sections = g_strsplit (*line, ":", 2);
|
||||
|
||||
if (g_strv_length (sections) < 2)
|
||||
continue;
|
||||
if (g_strcmp0 (g_strstrip (sections[1]), "AuthenticAMD") == 0)
|
||||
return PPD_PROBE_RESULT_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return PPD_PROBE_RESULT_FAIL;
|
||||
}
|
||||
|
||||
static void
|
||||
upower_name_vanished (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
gpointer user_data)
|
||||
{
|
||||
PpdActionAmdgpuPanelPower *self = user_data;
|
||||
|
||||
g_debug ("%s vanished", UPOWER_DBUS_NAME);
|
||||
|
||||
/* reset */
|
||||
g_clear_pointer (&self->proxy, g_object_unref);
|
||||
upower_properties_changed (NULL, NULL, NULL, self);
|
||||
}
|
||||
|
||||
static void
|
||||
upower_name_appeared (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
const gchar *name_owner,
|
||||
gpointer user_data)
|
||||
{
|
||||
PpdActionAmdgpuPanelPower *self = user_data;
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
g_debug ("%s appeared", UPOWER_DBUS_NAME);
|
||||
self->proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
|
||||
G_DBUS_PROXY_FLAGS_NONE,
|
||||
NULL,
|
||||
UPOWER_DBUS_NAME,
|
||||
UPOWER_DBUS_PATH,
|
||||
UPOWER_DBUS_INTERFACE,
|
||||
NULL,
|
||||
&error);
|
||||
if (self->proxy == NULL) {
|
||||
g_debug ("failed to connect to upower: %s", error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
g_signal_connect (self->proxy,
|
||||
"g-properties-changed",
|
||||
G_CALLBACK(upower_properties_changed),
|
||||
self);
|
||||
upower_properties_changed (self->proxy, NULL, NULL, self);
|
||||
}
|
||||
|
||||
static void
|
||||
ppd_action_amdgpu_panel_power_finalize (GObject *object)
|
||||
{
|
||||
PpdActionAmdgpuPanelPower *action;
|
||||
|
||||
action = PPD_ACTION_AMDGPU_PANEL_POWER (object);
|
||||
g_clear_handle_id (&action->watcher_id, g_bus_unwatch_name);
|
||||
g_clear_object (&action->client);
|
||||
g_clear_pointer (&action->proxy, g_object_unref);
|
||||
G_OBJECT_CLASS (ppd_action_amdgpu_panel_power_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
ppd_action_amdgpu_panel_power_class_init (PpdActionAmdgpuPanelPowerClass *klass)
|
||||
{
|
||||
GObjectClass *object_class;
|
||||
PpdActionClass *driver_class;
|
||||
|
||||
object_class = G_OBJECT_CLASS(klass);
|
||||
object_class->constructor = ppd_action_amdgpu_panel_power_constructor;
|
||||
object_class->finalize = ppd_action_amdgpu_panel_power_finalize;
|
||||
|
||||
driver_class = PPD_ACTION_CLASS(klass);
|
||||
driver_class->probe = ppd_action_amdgpu_panel_power_probe;
|
||||
driver_class->activate_profile = ppd_action_amdgpu_panel_power_activate_profile;
|
||||
}
|
||||
|
||||
static void
|
||||
ppd_action_amdgpu_panel_power_init (PpdActionAmdgpuPanelPower *self)
|
||||
{
|
||||
const gchar * const subsystem[] = { "drm", NULL };
|
||||
|
||||
self->client = g_udev_client_new (subsystem);
|
||||
g_signal_connect (G_OBJECT (self->client), "uevent",
|
||||
G_CALLBACK (udev_uevent_cb), self);
|
||||
|
||||
self->watcher_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM,
|
||||
UPOWER_DBUS_NAME,
|
||||
G_BUS_NAME_WATCHER_FLAGS_NONE,
|
||||
upower_name_appeared,
|
||||
upower_name_vanished,
|
||||
self,
|
||||
NULL);
|
||||
}
|
||||
15
src/ppd-action-amdgpu-panel-power.h
Normal file
15
src/ppd-action-amdgpu-panel-power.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Advanced Micro Devices
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 3 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ppd-action.h"
|
||||
|
||||
#define PPD_TYPE_ACTION_AMDGPU_PANEL_POWER (ppd_action_amdgpu_panel_power_get_type())
|
||||
G_DECLARE_FINAL_TYPE(PpdActionAmdgpuPanelPower, ppd_action_amdgpu_panel_power, PPD, ACTION_AMDGPU_PANEL_POWER, PpdAction)
|
||||
|
|
@ -320,6 +320,12 @@ class Tests(dbusmock.DBusTestCase):
|
|||
["DEVPATH", "/devices/platform/thinkpad_acpi"],
|
||||
)
|
||||
|
||||
def create_amd_apu(self):
|
||||
proc_dir = os.path.join(self.testbed.get_root_dir(), "proc/")
|
||||
os.makedirs(proc_dir)
|
||||
with open(os.path.join(proc_dir, "cpuinfo"), "w", encoding="ASCII") as cpu:
|
||||
cpu.write("vendor_id : AuthenticAMD\n")
|
||||
|
||||
def create_empty_platform_profile(self):
|
||||
acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
|
||||
os.makedirs(acpi_dir)
|
||||
|
|
@ -1358,6 +1364,56 @@ class Tests(dbusmock.DBusTestCase):
|
|||
profiles = self.get_dbus_property("Profiles")
|
||||
self.assertEqual(len(profiles), 2)
|
||||
|
||||
def test_amdgpu_panel_power(self):
|
||||
"""Verify AMDGPU Panel power actions"""
|
||||
edp = self.testbed.add_device(
|
||||
"drm",
|
||||
"card1-eDP",
|
||||
None,
|
||||
["amdgpu/panel_power_savings", "0"],
|
||||
["DEVTYPE", "drm_connector"],
|
||||
)
|
||||
|
||||
self.create_amd_apu()
|
||||
|
||||
self.start_daemon()
|
||||
|
||||
self.assertIn("amdgpu_panel_power", self.get_dbus_property("Actions"))
|
||||
|
||||
# verify it hasn't been updated yet due to missing upower
|
||||
self.assertEqual(self.read_sysfs_attr(edp, "amdgpu/panel_power_savings"), b"0")
|
||||
|
||||
# start upower and try again
|
||||
self.stop_daemon()
|
||||
self.spawn_server_template(
|
||||
"upower",
|
||||
{"DaemonVersion": "0.99", "OnBattery": True},
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
self.start_daemon()
|
||||
|
||||
# verify balanced updated it
|
||||
self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("balanced"))
|
||||
self.assertEqual(self.read_sysfs_attr(edp, "amdgpu/panel_power_savings"), b"3")
|
||||
|
||||
# verify power saver updated it
|
||||
self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver"))
|
||||
self.assertEqual(self.read_sysfs_attr(edp, "amdgpu/panel_power_savings"), b"4")
|
||||
|
||||
# add another device that supports the feature
|
||||
edp2 = self.testbed.add_device(
|
||||
"drm",
|
||||
"card2-eDP",
|
||||
None,
|
||||
["amdgpu/panel_power_savings", "0"],
|
||||
["DEVTYPE", "drm_connector"],
|
||||
)
|
||||
|
||||
# verify power saver got updated for it
|
||||
self.assert_eventually(
|
||||
lambda: self.read_sysfs_attr(edp2, "amdgpu/panel_power_savings") == b"4"
|
||||
)
|
||||
|
||||
def test_trickle_charge_system(self):
|
||||
"""Trickle power_supply charge type"""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue