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:
Mario Limonciello 2024-01-24 20:47:40 -06:00 committed by Marco Trevisan
parent 9a740c02d6
commit 41ed5d33a8
8 changed files with 414 additions and 0 deletions

View file

@ -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',

View file

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

View file

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

View file

@ -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',

View file

@ -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 {

View 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);
}

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

View file

@ -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"""