Line | Branch | Exec | Source |
---|---|---|---|
1 | /* | ||
2 | * Copyright (c) 2024 Advanced Micro Devices | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify it | ||
5 | * under the terms of the GNU General Public License version 3 as published by | ||
6 | * the Free Software Foundation. | ||
7 | * | ||
8 | */ | ||
9 | |||
10 | #define G_LOG_DOMAIN "AmdgpuPanel" | ||
11 | |||
12 | #include "config.h" | ||
13 | |||
14 | #include <gudev/gudev.h> | ||
15 | |||
16 | #include "ppd-action-amdgpu-panel-power.h" | ||
17 | #include "ppd-profile.h" | ||
18 | #include "ppd-utils.h" | ||
19 | |||
20 | #define PANEL_POWER_SYSFS_NAME "amdgpu/panel_power_savings" | ||
21 | #define PANEL_STATUS_SYSFS_NAME "status" | ||
22 | |||
23 | /** | ||
24 | * SECTION:ppd-action-amdgpu-panel-power | ||
25 | * @Short_description: Power savings for eDP connected displays | ||
26 | * @Title: AMDGPU Panel power action | ||
27 | * | ||
28 | * The AMDGPU panel power action utilizes the sysfs attribute present on some DRM | ||
29 | * connectors for amdgpu called "panel_power_savings". This will use an AMD specific | ||
30 | * hardware feature for a power savings profile for the panel. | ||
31 | * | ||
32 | */ | ||
33 | |||
34 | struct _PpdActionAmdgpuPanelPower | ||
35 | { | ||
36 | PpdAction parent_instance; | ||
37 | PpdProfile last_profile; | ||
38 | |||
39 | GUdevClient *client; | ||
40 | |||
41 | gint panel_power_saving; | ||
42 | gboolean valid_battery; | ||
43 | gboolean on_battery; | ||
44 | gdouble battery_level; | ||
45 | }; | ||
46 | |||
47 |
4/5✓ Branch 0 taken 130 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 130 times.
✓ Branch 3 taken 130 times.
✗ Branch 4 not taken.
|
788 | G_DEFINE_TYPE (PpdActionAmdgpuPanelPower, ppd_action_amdgpu_panel_power, PPD_TYPE_ACTION) |
48 | |||
49 | static GObject* | ||
50 | 134 | ppd_action_amdgpu_panel_power_constructor (GType type, | |
51 | guint n_construct_params, | ||
52 | GObjectConstructParam *construct_params) | ||
53 | { | ||
54 | 134 | GObject *object; | |
55 | |||
56 | 134 | object = G_OBJECT_CLASS (ppd_action_amdgpu_panel_power_parent_class)->constructor (type, | |
57 | n_construct_params, | ||
58 | construct_params); | ||
59 | 134 | g_object_set (object, | |
60 | "action-name", "amdgpu_panel_power", | ||
61 | NULL); | ||
62 | |||
63 | 134 | return object; | |
64 | } | ||
65 | |||
66 | static gboolean | ||
67 | 18 | panel_connected (GUdevDevice *device) | |
68 | { | ||
69 | 18 | const char *value; | |
70 | 36 | g_autofree gchar *stripped = NULL; | |
71 | |||
72 | 18 | value = g_udev_device_get_sysfs_attr_uncached (device, PANEL_STATUS_SYSFS_NAME); | |
73 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
18 | if (!value) |
74 | return FALSE; | ||
75 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
|
36 | stripped = g_strchomp (g_strdup (value)); |
76 | |||
77 | 18 | return g_strcmp0 (stripped, "connected") == 0; | |
78 | } | ||
79 | |||
80 | static gboolean | ||
81 | 14 | set_panel_power (PpdActionAmdgpuPanelPower *self, gint power, GError **error) | |
82 | { | ||
83 | 14 | GList *devices, *l; | |
84 | |||
85 | 14 | devices = g_udev_client_query_by_subsystem (self->client, "drm"); | |
86 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
|
14 | if (devices == NULL) { |
87 | ✗ | g_set_error_literal (error, | |
88 | G_IO_ERROR, | ||
89 | G_IO_ERROR_NOT_FOUND, | ||
90 | "no drm devices found"); | ||
91 | ✗ | return FALSE; | |
92 | } | ||
93 | |||
94 |
2/2✓ Branch 0 taken 14 times.
✓ Branch 1 taken 6 times.
|
20 | for (l = devices; l != NULL; l = l->next) { |
95 | 14 | GUdevDevice *dev = l->data; | |
96 | 14 | const char *value; | |
97 | 14 | guint64 parsed; | |
98 | |||
99 | 14 | value = g_udev_device_get_devtype (dev); | |
100 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
|
14 | if (g_strcmp0 (value, "drm_connector") != 0) |
101 | ✗ | continue; | |
102 | |||
103 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
|
14 | if (!panel_connected (dev)) |
104 | ✗ | continue; | |
105 | |||
106 | 14 | value = g_udev_device_get_sysfs_attr_uncached (dev, PANEL_POWER_SYSFS_NAME); | |
107 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
|
14 | if (!value) |
108 | ✗ | continue; | |
109 | |||
110 | 14 | parsed = g_ascii_strtoull (value, NULL, 10); | |
111 | |||
112 | /* overflow check */ | ||
113 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
|
14 | if (parsed == G_MAXUINT64) { |
114 | ✗ | g_set_error (error, | |
115 | G_IO_ERROR, | ||
116 | G_IO_ERROR_INVALID_DATA, | ||
117 | "cannot parse %s as caused overflow", | ||
118 | value); | ||
119 | ✗ | return FALSE; | |
120 | } | ||
121 | |||
122 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 8 times.
|
14 | if (parsed == power) |
123 | 6 | continue; | |
124 | |||
125 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
|
8 | if (!ppd_utils_write_sysfs_int (dev, PANEL_POWER_SYSFS_NAME, power, error)) |
126 | return FALSE; | ||
127 | |||
128 | break; | ||
129 | } | ||
130 | |||
131 | 14 | g_list_free_full (devices, g_object_unref); | |
132 | |||
133 | 14 | return TRUE; | |
134 | } | ||
135 | |||
136 | static gboolean | ||
137 | 14 | ppd_action_amdgpu_panel_update_target (PpdActionAmdgpuPanelPower *self, | |
138 | GError **error) | ||
139 | { | ||
140 | 14 | gint target = 0; | |
141 | |||
142 | /* only activate if we know that we're on battery */ | ||
143 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | if (self->on_battery) { |
144 |
2/3✓ Branch 0 taken 6 times.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
14 | switch (self->last_profile) { |
145 | 6 | case PPD_PROFILE_POWER_SAVER: | |
146 |
3/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 2 times.
|
6 | if (!self->battery_level || self->battery_level >= 50) |
147 | target = 0; | ||
148 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | else if (self->battery_level > 30) |
149 | target = 1; | ||
150 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | else if (self->battery_level > 20 && self->battery_level <= 30) |
151 | target = 2; | ||
152 | else /* < 20 */ | ||
153 | 2 | target = 3; | |
154 | break; | ||
155 | 8 | case PPD_PROFILE_BALANCED: | |
156 |
4/4✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 4 times.
|
8 | if (!self->battery_level || self->battery_level >= 30) |
157 | target = 0; | ||
158 | else | ||
159 | 4 | target = 1; | |
160 | break; | ||
161 | case PPD_PROFILE_PERFORMANCE: | ||
162 | target = 0; | ||
163 | break; | ||
164 | } | ||
165 | } | ||
166 | |||
167 | 14 | g_info("Updating panel to %d due to 🔋 %d (%f)", target, self->on_battery, self->battery_level); | |
168 |
1/2✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
|
14 | if (!set_panel_power (self, target, error)) |
169 | return FALSE; | ||
170 | 14 | self->panel_power_saving = target; | |
171 | |||
172 | 14 | return TRUE; | |
173 | } | ||
174 | |||
175 | static gboolean | ||
176 | 14 | ppd_action_amdgpu_panel_power_activate_profile (PpdAction *action, | |
177 | PpdProfile profile, | ||
178 | GError **error) | ||
179 | { | ||
180 | 14 | PpdActionAmdgpuPanelPower *self = PPD_ACTION_AMDGPU_PANEL_POWER (action); | |
181 | 14 | self->last_profile = profile; | |
182 | |||
183 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 2 times.
|
14 | if (!self->valid_battery) { |
184 | 12 | g_debug ("upower not available; battery data might be stale"); | |
185 | 12 | return TRUE; | |
186 | } | ||
187 | |||
188 | 2 | return ppd_action_amdgpu_panel_update_target (self, error); | |
189 | } | ||
190 | |||
191 | static gboolean | ||
192 | 4 | ppd_action_amdgpu_panel_power_power_changed (PpdAction *action, | |
193 | PpdPowerChangedReason reason, | ||
194 | GError **error) | ||
195 | { | ||
196 | 4 | PpdActionAmdgpuPanelPower *self = PPD_ACTION_AMDGPU_PANEL_POWER (action); | |
197 | |||
198 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
4 | switch (reason) { |
199 | 2 | case PPD_POWER_CHANGED_REASON_UNKNOWN: | |
200 | 2 | self->valid_battery = FALSE; | |
201 | 2 | return TRUE; | |
202 | ✗ | case PPD_POWER_CHANGED_REASON_AC: | |
203 | ✗ | self->on_battery = FALSE; | |
204 | ✗ | break; | |
205 | 2 | case PPD_POWER_CHANGED_REASON_BATTERY: | |
206 | 2 | self->on_battery = TRUE; | |
207 | 2 | break; | |
208 | ✗ | default: | |
209 | ✗ | g_assert_not_reached (); | |
210 | } | ||
211 | |||
212 | 2 | self->valid_battery = TRUE; | |
213 | |||
214 | 2 | return ppd_action_amdgpu_panel_update_target (self, error); | |
215 | } | ||
216 | |||
217 | static gboolean | ||
218 | 10 | ppd_action_amdgpu_panel_power_battery_changed (PpdAction *action, | |
219 | gdouble val, | ||
220 | GError **error) | ||
221 | { | ||
222 | 10 | PpdActionAmdgpuPanelPower *self = PPD_ACTION_AMDGPU_PANEL_POWER (action); | |
223 | |||
224 | 10 | self->battery_level = val; | |
225 | |||
226 | 10 | return ppd_action_amdgpu_panel_update_target (self, error); | |
227 | } | ||
228 | |||
229 | static void | ||
230 | 4 | udev_uevent_cb (GUdevClient *client, | |
231 | gchar *action, | ||
232 | GUdevDevice *device, | ||
233 | gpointer user_data) | ||
234 | { | ||
235 | 4 | PpdActionAmdgpuPanelPower *self = user_data; | |
236 | |||
237 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (!g_str_equal (action, "add")) |
238 | return; | ||
239 | |||
240 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | if (!g_udev_device_has_sysfs_attr (device, PANEL_POWER_SYSFS_NAME)) |
241 | return; | ||
242 | |||
243 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
|
4 | if (!panel_connected (device)) |
244 | return; | ||
245 | |||
246 | 2 | g_debug ("Updating panel power saving for '%s' to '%d'", | |
247 | g_udev_device_get_sysfs_path (device), | ||
248 | self->panel_power_saving); | ||
249 | 2 | ppd_utils_write_sysfs_int (device, PANEL_POWER_SYSFS_NAME, | |
250 | 2 | self->panel_power_saving, NULL); | |
251 | } | ||
252 | |||
253 | static PpdProbeResult | ||
254 | 132 | ppd_action_amdgpu_panel_power_probe (PpdAction *action) | |
255 | { | ||
256 | 132 | return ppd_utils_match_cpu_vendor ("AuthenticAMD") ? PPD_PROBE_RESULT_SUCCESS : PPD_PROBE_RESULT_FAIL; | |
257 | } | ||
258 | |||
259 | static void | ||
260 | 134 | ppd_action_amdgpu_panel_power_finalize (GObject *object) | |
261 | { | ||
262 | 134 | PpdActionAmdgpuPanelPower *action; | |
263 | |||
264 | 134 | action = PPD_ACTION_AMDGPU_PANEL_POWER (object); | |
265 |
1/2✓ Branch 0 taken 134 times.
✗ Branch 1 not taken.
|
134 | g_clear_object (&action->client); |
266 | 134 | G_OBJECT_CLASS (ppd_action_amdgpu_panel_power_parent_class)->finalize (object); | |
267 | 134 | } | |
268 | |||
269 | static void | ||
270 | 130 | ppd_action_amdgpu_panel_power_class_init (PpdActionAmdgpuPanelPowerClass *klass) | |
271 | { | ||
272 | 130 | GObjectClass *object_class; | |
273 | 130 | PpdActionClass *driver_class; | |
274 | |||
275 | 130 | object_class = G_OBJECT_CLASS(klass); | |
276 | 130 | object_class->constructor = ppd_action_amdgpu_panel_power_constructor; | |
277 | 130 | object_class->finalize = ppd_action_amdgpu_panel_power_finalize; | |
278 | |||
279 | 130 | driver_class = PPD_ACTION_CLASS(klass); | |
280 | 130 | driver_class->probe = ppd_action_amdgpu_panel_power_probe; | |
281 | 130 | driver_class->activate_profile = ppd_action_amdgpu_panel_power_activate_profile; | |
282 | 130 | driver_class->power_changed = ppd_action_amdgpu_panel_power_power_changed; | |
283 | 130 | driver_class->battery_changed = ppd_action_amdgpu_panel_power_battery_changed; | |
284 | } | ||
285 | |||
286 | static void | ||
287 | 134 | ppd_action_amdgpu_panel_power_init (PpdActionAmdgpuPanelPower *self) | |
288 | { | ||
289 | 134 | const gchar * const subsystem[] = { "drm", NULL }; | |
290 | |||
291 | 134 | self->client = g_udev_client_new (subsystem); | |
292 | 134 | g_signal_connect_object (G_OBJECT (self->client), "uevent", | |
293 | G_CALLBACK (udev_uevent_cb), self, 0); | ||
294 | 134 | } | |
295 |