Skip to content

Commit 1452c25

Browse files
committed
drm: Add helpers to kick off self refresh mode in drivers
This patch adds a new drm helper library to help drivers implement self refresh. Drivers choosing to use it will register crtcs and will receive callbacks when it's time to enter or exit self refresh mode. In its current form, it has a timer which will trigger after a driver-specified amount of inactivity. When the timer triggers, the helpers will submit a new atomic commit to shut the refreshing pipe off. On the next atomic commit, the drm core will revert the self refresh state and bring everything back up to be actively driven. From the driver's perspective, this works like a regular disable/enable cycle. The driver need only check the 'self_refresh_active' state in crtc_state. It should initiate self refresh mode on the panel and enter an off or low-power state. Changes in v2: - s/psr/self_refresh/ (Daniel) - integrated the psr exit into the commit that wakes it up (Jose/Daniel) - made the psr state per-crtc (Jose/Daniel) Changes in v3: - Remove the self_refresh_(active|changed) from connector state (Daniel) - Simplify loop in drm_self_refresh_helper_alter_state (Daniel) - Improve self_refresh_aware comment (Daniel) - s/self_refresh_state/self_refresh_data/ (Daniel) Changes in v4: - Move docbook location below panel (Daniel) - Improve docbook with references and more detailed explanation (Daniel) - Instead of register/unregister, use init/cleanup (Daniel) Changes in v5: - Resolved conflict in drm_atomic_helper.c #include block - Resolved conflict in rst with HDCP helper docs Changes in v6: - Fix include ordering, clean up forward declarations (Sam) Link to v1: https://patchwork.freedesktop.org/patch/msgid/[email protected] Link to v2: https://patchwork.freedesktop.org/patch/msgid/[email protected] Link to v3: https://patchwork.freedesktop.org/patch/msgid/[email protected] Link to v4: https://patchwork.freedesktop.org/patch/msgid/[email protected] Link to v5: https://patchwork.freedesktop.org/patch/msgid/[email protected] Cc: Daniel Vetter <[email protected]> Cc: Jose Souza <[email protected]> Cc: Zain Wang <[email protected]> Cc: Tomasz Figa <[email protected]> Cc: Ville Syrjälä <[email protected]> Cc: Sam Ravnborg <[email protected]> Tested-by: Heiko Stuebner <[email protected]> Reviewed-by: Daniel Vetter <[email protected]> Signed-off-by: Sean Paul <[email protected]> Link: https://patchwork.freedesktop.org/patch/msgid/[email protected]
1 parent 6f3b627 commit 1452c25

File tree

11 files changed

+338
-5
lines changed

11 files changed

+338
-5
lines changed

Documentation/gpu/drm-kms-helpers.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,15 @@ Panel Helper Reference
181181
.. kernel-doc:: drivers/gpu/drm/drm_panel_orientation_quirks.c
182182
:export:
183183

184+
Panel Self Refresh Helper Reference
185+
===================================
186+
187+
.. kernel-doc:: drivers/gpu/drm/drm_self_refresh_helper.c
188+
:doc: overview
189+
190+
.. kernel-doc:: drivers/gpu/drm/drm_self_refresh_helper.c
191+
:export:
192+
184193
HDCP Helper Functions Reference
185194
===============================
186195

drivers/gpu/drm/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_dsc.o drm_probe_helper
4343
drm_simple_kms_helper.o drm_modeset_helper.o \
4444
drm_scdc_helper.o drm_gem_framebuffer_helper.o \
4545
drm_atomic_state_helper.o drm_damage_helper.o \
46-
drm_format_helper.o
46+
drm_format_helper.o drm_self_refresh_helper.o
4747

4848
drm_kms_helper-$(CONFIG_DRM_PANEL_BRIDGE) += bridge/panel.o
4949
drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o

drivers/gpu/drm/drm_atomic.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p,
384384
drm_printf(p, "crtc[%u]: %s\n", crtc->base.id, crtc->name);
385385
drm_printf(p, "\tenable=%d\n", state->enable);
386386
drm_printf(p, "\tactive=%d\n", state->active);
387+
drm_printf(p, "\tself_refresh_active=%d\n", state->self_refresh_active);
387388
drm_printf(p, "\tplanes_changed=%d\n", state->planes_changed);
388389
drm_printf(p, "\tmode_changed=%d\n", state->mode_changed);
389390
drm_printf(p, "\tactive_changed=%d\n", state->active_changed);
@@ -999,6 +1000,7 @@ static void drm_atomic_connector_print_state(struct drm_printer *p,
9991000

10001001
drm_printf(p, "connector[%u]: %s\n", connector->base.id, connector->name);
10011002
drm_printf(p, "\tcrtc=%s\n", state->crtc ? state->crtc->name : "(null)");
1003+
drm_printf(p, "\tself_refresh_aware=%d\n", state->self_refresh_aware);
10021004

10031005
if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
10041006
if (state->writeback_job && state->writeback_job->fb)

drivers/gpu/drm/drm_atomic_helper.c

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include <drm/drm_device.h>
3535
#include <drm/drm_plane_helper.h>
3636
#include <drm/drm_print.h>
37+
#include <drm/drm_self_refresh_helper.h>
3738
#include <drm/drm_vblank.h>
3839
#include <drm/drm_writeback.h>
3940

@@ -953,10 +954,33 @@ int drm_atomic_helper_check(struct drm_device *dev,
953954
if (state->legacy_cursor_update)
954955
state->async_update = !drm_atomic_helper_async_check(dev, state);
955956

957+
drm_self_refresh_helper_alter_state(state);
958+
956959
return ret;
957960
}
958961
EXPORT_SYMBOL(drm_atomic_helper_check);
959962

963+
static bool
964+
crtc_needs_disable(struct drm_crtc_state *old_state,
965+
struct drm_crtc_state *new_state)
966+
{
967+
/*
968+
* No new_state means the crtc is off, so the only criteria is whether
969+
* it's currently active or in self refresh mode.
970+
*/
971+
if (!new_state)
972+
return drm_atomic_crtc_effectively_active(old_state);
973+
974+
/*
975+
* We need to run through the crtc_funcs->disable() function if the crtc
976+
* is currently on, if it's transitioning to self refresh mode, or if
977+
* it's in self refresh mode and needs to be fully disabled.
978+
*/
979+
return old_state->active ||
980+
(old_state->self_refresh_active && !new_state->enable) ||
981+
new_state->self_refresh_active;
982+
}
983+
960984
static void
961985
disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state)
962986
{
@@ -977,7 +1001,14 @@ disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state)
9771001

9781002
old_crtc_state = drm_atomic_get_old_crtc_state(old_state, old_conn_state->crtc);
9791003

980-
if (!old_crtc_state->active ||
1004+
if (new_conn_state->crtc)
1005+
new_crtc_state = drm_atomic_get_new_crtc_state(
1006+
old_state,
1007+
new_conn_state->crtc);
1008+
else
1009+
new_crtc_state = NULL;
1010+
1011+
if (!crtc_needs_disable(old_crtc_state, new_crtc_state) ||
9811012
!drm_atomic_crtc_needs_modeset(old_conn_state->crtc->state))
9821013
continue;
9831014

@@ -1023,7 +1054,7 @@ disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state)
10231054
if (!drm_atomic_crtc_needs_modeset(new_crtc_state))
10241055
continue;
10251056

1026-
if (!old_crtc_state->active)
1057+
if (!crtc_needs_disable(old_crtc_state, new_crtc_state))
10271058
continue;
10281059

10291060
funcs = crtc->helper_private;

drivers/gpu/drm/drm_atomic_state_helper.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ void __drm_atomic_helper_crtc_duplicate_state(struct drm_crtc *crtc,
129129
state->commit = NULL;
130130
state->event = NULL;
131131
state->pageflip_flags = 0;
132+
133+
/* Self refresh should be canceled when a new update is available */
134+
state->active = drm_atomic_crtc_effectively_active(state);
135+
state->self_refresh_active = false;
132136
}
133137
EXPORT_SYMBOL(__drm_atomic_helper_crtc_duplicate_state);
134138

drivers/gpu/drm/drm_atomic_uapi.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ drm_atomic_crtc_get_property(struct drm_crtc *crtc,
490490
struct drm_mode_config *config = &dev->mode_config;
491491

492492
if (property == config->prop_active)
493-
*val = state->active;
493+
*val = drm_atomic_crtc_effectively_active(state);
494494
else if (property == config->prop_mode_id)
495495
*val = (state->mode_blob) ? state->mode_blob->base.id : 0;
496496
else if (property == config->prop_vrr_enabled)
@@ -788,7 +788,10 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
788788
if (property == config->prop_crtc_id) {
789789
*val = (state->crtc) ? state->crtc->base.id : 0;
790790
} else if (property == config->dpms_property) {
791-
*val = connector->dpms;
791+
if (state->crtc && state->crtc->state->self_refresh_active)
792+
*val = DRM_MODE_DPMS_ON;
793+
else
794+
*val = connector->dpms;
792795
} else if (property == config->tv_select_subconnector_property) {
793796
*val = state->tv.subconnector;
794797
} else if (property == config->tv_left_margin_property) {
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
// SPDX-License-Identifier: MIT
2+
/*
3+
* Copyright (C) 2019 Google, Inc.
4+
*
5+
* Authors:
6+
* Sean Paul <[email protected]>
7+
*/
8+
#include <linux/bitops.h>
9+
#include <linux/slab.h>
10+
#include <linux/workqueue.h>
11+
12+
#include <drm/drm_atomic.h>
13+
#include <drm/drm_atomic_helper.h>
14+
#include <drm/drm_connector.h>
15+
#include <drm/drm_crtc.h>
16+
#include <drm/drm_device.h>
17+
#include <drm/drm_mode_config.h>
18+
#include <drm/drm_modeset_lock.h>
19+
#include <drm/drm_print.h>
20+
#include <drm/drm_self_refresh_helper.h>
21+
22+
/**
23+
* DOC: overview
24+
*
25+
* This helper library provides an easy way for drivers to leverage the atomic
26+
* framework to implement panel self refresh (SR) support. Drivers are
27+
* responsible for initializing and cleaning up the SR helpers on load/unload
28+
* (see &drm_self_refresh_helper_init/&drm_self_refresh_helper_cleanup).
29+
* The connector is responsible for setting
30+
* &drm_connector_state.self_refresh_aware to true at runtime if it is SR-aware
31+
* (meaning it knows how to initiate self refresh on the panel).
32+
*
33+
* Once a crtc has enabled SR using &drm_self_refresh_helper_init, the
34+
* helpers will monitor activity and call back into the driver to enable/disable
35+
* SR as appropriate. The best way to think about this is that it's a DPMS
36+
* on/off request with &drm_crtc_state.self_refresh_active set in crtc state
37+
* that tells you to disable/enable SR on the panel instead of power-cycling it.
38+
*
39+
* During SR, drivers may choose to fully disable their crtc/encoder/bridge
40+
* hardware (in which case no driver changes are necessary), or they can inspect
41+
* &drm_crtc_state.self_refresh_active if they want to enter low power mode
42+
* without full disable (in case full disable/enable is too slow).
43+
*
44+
* SR will be deactivated if there are any atomic updates affecting the
45+
* pipe that is in SR mode. If a crtc is driving multiple connectors, all
46+
* connectors must be SR aware and all will enter/exit SR mode at the same time.
47+
*
48+
* If the crtc and connector are SR aware, but the panel connected does not
49+
* support it (or is otherwise unable to enter SR), the driver should fail
50+
* atomic_check when &drm_crtc_state.self_refresh_active is true.
51+
*/
52+
53+
struct drm_self_refresh_data {
54+
struct drm_crtc *crtc;
55+
struct delayed_work entry_work;
56+
struct drm_atomic_state *save_state;
57+
unsigned int entry_delay_ms;
58+
};
59+
60+
static void drm_self_refresh_helper_entry_work(struct work_struct *work)
61+
{
62+
struct drm_self_refresh_data *sr_data = container_of(
63+
to_delayed_work(work),
64+
struct drm_self_refresh_data, entry_work);
65+
struct drm_crtc *crtc = sr_data->crtc;
66+
struct drm_device *dev = crtc->dev;
67+
struct drm_modeset_acquire_ctx ctx;
68+
struct drm_atomic_state *state;
69+
struct drm_connector *conn;
70+
struct drm_connector_state *conn_state;
71+
struct drm_crtc_state *crtc_state;
72+
int i, ret;
73+
74+
drm_modeset_acquire_init(&ctx, 0);
75+
76+
state = drm_atomic_state_alloc(dev);
77+
if (!state) {
78+
ret = -ENOMEM;
79+
goto out;
80+
}
81+
82+
retry:
83+
state->acquire_ctx = &ctx;
84+
85+
crtc_state = drm_atomic_get_crtc_state(state, crtc);
86+
if (IS_ERR(crtc_state)) {
87+
ret = PTR_ERR(crtc_state);
88+
goto out;
89+
}
90+
91+
if (!crtc_state->enable)
92+
goto out;
93+
94+
ret = drm_atomic_add_affected_connectors(state, crtc);
95+
if (ret)
96+
goto out;
97+
98+
for_each_new_connector_in_state(state, conn, conn_state, i) {
99+
if (!conn_state->self_refresh_aware)
100+
goto out;
101+
}
102+
103+
crtc_state->active = false;
104+
crtc_state->self_refresh_active = true;
105+
106+
ret = drm_atomic_commit(state);
107+
if (ret)
108+
goto out;
109+
110+
out:
111+
if (ret == -EDEADLK) {
112+
drm_atomic_state_clear(state);
113+
ret = drm_modeset_backoff(&ctx);
114+
if (!ret)
115+
goto retry;
116+
}
117+
118+
drm_atomic_state_put(state);
119+
drm_modeset_drop_locks(&ctx);
120+
drm_modeset_acquire_fini(&ctx);
121+
}
122+
123+
/**
124+
* drm_self_refresh_helper_alter_state - Alters the atomic state for SR exit
125+
* @state: the state currently being checked
126+
*
127+
* Called at the end of atomic check. This function checks the state for flags
128+
* incompatible with self refresh exit and changes them. This is a bit
129+
* disingenuous since userspace is expecting one thing and we're giving it
130+
* another. However in order to keep self refresh entirely hidden from
131+
* userspace, this is required.
132+
*
133+
* At the end, we queue up the self refresh entry work so we can enter PSR after
134+
* the desired delay.
135+
*/
136+
void drm_self_refresh_helper_alter_state(struct drm_atomic_state *state)
137+
{
138+
struct drm_crtc *crtc;
139+
struct drm_crtc_state *crtc_state;
140+
int i;
141+
142+
if (state->async_update || !state->allow_modeset) {
143+
for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
144+
if (crtc_state->self_refresh_active) {
145+
state->async_update = false;
146+
state->allow_modeset = true;
147+
break;
148+
}
149+
}
150+
}
151+
152+
for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
153+
struct drm_self_refresh_data *sr_data;
154+
155+
/* Don't trigger the entry timer when we're already in SR */
156+
if (crtc_state->self_refresh_active)
157+
continue;
158+
159+
sr_data = crtc->self_refresh_data;
160+
if (!sr_data)
161+
continue;
162+
163+
mod_delayed_work(system_wq, &sr_data->entry_work,
164+
msecs_to_jiffies(sr_data->entry_delay_ms));
165+
}
166+
}
167+
EXPORT_SYMBOL(drm_self_refresh_helper_alter_state);
168+
169+
/**
170+
* drm_self_refresh_helper_init - Initializes self refresh helpers for a crtc
171+
* @crtc: the crtc which supports self refresh supported displays
172+
* @entry_delay_ms: amount of inactivity to wait before entering self refresh
173+
*
174+
* Returns zero if successful or -errno on failure
175+
*/
176+
int drm_self_refresh_helper_init(struct drm_crtc *crtc,
177+
unsigned int entry_delay_ms)
178+
{
179+
struct drm_self_refresh_data *sr_data = crtc->self_refresh_data;
180+
181+
/* Helper is already initialized */
182+
if (WARN_ON(sr_data))
183+
return -EINVAL;
184+
185+
sr_data = kzalloc(sizeof(*sr_data), GFP_KERNEL);
186+
if (!sr_data)
187+
return -ENOMEM;
188+
189+
INIT_DELAYED_WORK(&sr_data->entry_work,
190+
drm_self_refresh_helper_entry_work);
191+
sr_data->entry_delay_ms = entry_delay_ms;
192+
sr_data->crtc = crtc;
193+
194+
crtc->self_refresh_data = sr_data;
195+
return 0;
196+
}
197+
EXPORT_SYMBOL(drm_self_refresh_helper_init);
198+
199+
/**
200+
* drm_self_refresh_helper_cleanup - Cleans up self refresh helpers for a crtc
201+
* @crtc: the crtc to cleanup
202+
*/
203+
void drm_self_refresh_helper_cleanup(struct drm_crtc *crtc)
204+
{
205+
struct drm_self_refresh_data *sr_data = crtc->self_refresh_data;
206+
207+
/* Helper is already uninitialized */
208+
if (sr_data)
209+
return;
210+
211+
crtc->self_refresh_data = NULL;
212+
213+
cancel_delayed_work_sync(&sr_data->entry_work);
214+
kfree(sr_data);
215+
}
216+
EXPORT_SYMBOL(drm_self_refresh_helper_cleanup);

include/drm/drm_atomic.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,4 +957,19 @@ drm_atomic_crtc_needs_modeset(const struct drm_crtc_state *state)
957957
state->connectors_changed;
958958
}
959959

960+
/**
961+
* drm_atomic_crtc_effectively_active - compute whether crtc is actually active
962+
* @state: &drm_crtc_state for the CRTC
963+
*
964+
* When in self refresh mode, the crtc_state->active value will be false, since
965+
* the crtc is off. However in some cases we're interested in whether the crtc
966+
* is active, or effectively active (ie: it's connected to an active display).
967+
* In these cases, use this function instead of just checking active.
968+
*/
969+
static inline bool
970+
drm_atomic_crtc_effectively_active(const struct drm_crtc_state *state)
971+
{
972+
return state->active || state->self_refresh_active;
973+
}
974+
960975
#endif /* DRM_ATOMIC_H_ */

include/drm/drm_connector.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,20 @@ struct drm_connector_state {
548548
/** @tv: TV connector state */
549549
struct drm_tv_connector_state tv;
550550

551+
/**
552+
* @self_refresh_aware:
553+
*
554+
* This tracks whether a connector is aware of the self refresh state.
555+
* It should be set to true for those connector implementations which
556+
* understand the self refresh state. This is needed since the crtc
557+
* registers the self refresh helpers and it doesn't know if the
558+
* connectors downstream have implemented self refresh entry/exit.
559+
*
560+
* Drivers should set this to true in atomic_check if they know how to
561+
* handle self_refresh requests.
562+
*/
563+
bool self_refresh_aware;
564+
551565
/**
552566
* @picture_aspect_ratio: Connector property to control the
553567
* HDMI infoframe aspect ratio setting.

0 commit comments

Comments
 (0)