Skip to content

Commit

Permalink
drm: Add helpers to kick off self refresh mode in drivers
Browse files Browse the repository at this point in the history
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/20190228210939.83386-2-sean@poorly.run
Link to v2: https://patchwork.freedesktop.org/patch/msgid/20190326204509.96515-1-sean@poorly.run
Link to v3: https://patchwork.freedesktop.org/patch/msgid/20190502194956.218441-6-sean@poorly.run
Link to v4: https://patchwork.freedesktop.org/patch/msgid/20190508160920.144739-6-sean@poorly.run
Link to v5: https://patchwork.freedesktop.org/patch/msgid/20190611160844.257498-6-sean@poorly.run

Cc: Daniel Vetter <daniel@ffwll.ch>
Cc: Jose Souza <jose.souza@intel.com>
Cc: Zain Wang <wzz@rock-chips.com>
Cc: Tomasz Figa <tfiga@chromium.org>
Cc: Ville Syrjälä <ville.syrjala@linux.intel.com>
Cc: Sam Ravnborg <sam@ravnborg.org>
Tested-by: Heiko Stuebner <heiko@sntech.de>
Reviewed-by: Daniel Vetter <daniel@ffwll.ch>
Signed-off-by: Sean Paul <seanpaul@chromium.org>
Link: https://patchwork.freedesktop.org/patch/msgid/20190612145026.191846-1-sean@poorly.run
  • Loading branch information
atseanpaul committed Jun 13, 2019
1 parent 6f3b627 commit 1452c25
Show file tree
Hide file tree
Showing 11 changed files with 338 additions and 5 deletions.
9 changes: 9 additions & 0 deletions Documentation/gpu/drm-kms-helpers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,15 @@ Panel Helper Reference
.. kernel-doc:: drivers/gpu/drm/drm_panel_orientation_quirks.c
:export:

Panel Self Refresh Helper Reference
===================================

.. kernel-doc:: drivers/gpu/drm/drm_self_refresh_helper.c
:doc: overview

.. kernel-doc:: drivers/gpu/drm/drm_self_refresh_helper.c
:export:

HDCP Helper Functions Reference
===============================

Expand Down
2 changes: 1 addition & 1 deletion drivers/gpu/drm/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_dsc.o drm_probe_helper
drm_simple_kms_helper.o drm_modeset_helper.o \
drm_scdc_helper.o drm_gem_framebuffer_helper.o \
drm_atomic_state_helper.o drm_damage_helper.o \
drm_format_helper.o
drm_format_helper.o drm_self_refresh_helper.o

drm_kms_helper-$(CONFIG_DRM_PANEL_BRIDGE) += bridge/panel.o
drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o
Expand Down
2 changes: 2 additions & 0 deletions drivers/gpu/drm/drm_atomic.c
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p,
drm_printf(p, "crtc[%u]: %s\n", crtc->base.id, crtc->name);
drm_printf(p, "\tenable=%d\n", state->enable);
drm_printf(p, "\tactive=%d\n", state->active);
drm_printf(p, "\tself_refresh_active=%d\n", state->self_refresh_active);
drm_printf(p, "\tplanes_changed=%d\n", state->planes_changed);
drm_printf(p, "\tmode_changed=%d\n", state->mode_changed);
drm_printf(p, "\tactive_changed=%d\n", state->active_changed);
Expand Down Expand Up @@ -999,6 +1000,7 @@ static void drm_atomic_connector_print_state(struct drm_printer *p,

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

if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
if (state->writeback_job && state->writeback_job->fb)
Expand Down
35 changes: 33 additions & 2 deletions drivers/gpu/drm/drm_atomic_helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include <drm/drm_device.h>
#include <drm/drm_plane_helper.h>
#include <drm/drm_print.h>
#include <drm/drm_self_refresh_helper.h>
#include <drm/drm_vblank.h>
#include <drm/drm_writeback.h>

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

drm_self_refresh_helper_alter_state(state);

return ret;
}
EXPORT_SYMBOL(drm_atomic_helper_check);

static bool
crtc_needs_disable(struct drm_crtc_state *old_state,
struct drm_crtc_state *new_state)
{
/*
* No new_state means the crtc is off, so the only criteria is whether
* it's currently active or in self refresh mode.
*/
if (!new_state)
return drm_atomic_crtc_effectively_active(old_state);

/*
* We need to run through the crtc_funcs->disable() function if the crtc
* is currently on, if it's transitioning to self refresh mode, or if
* it's in self refresh mode and needs to be fully disabled.
*/
return old_state->active ||
(old_state->self_refresh_active && !new_state->enable) ||
new_state->self_refresh_active;
}

static void
disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state)
{
Expand All @@ -977,7 +1001,14 @@ disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state)

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

if (!old_crtc_state->active ||
if (new_conn_state->crtc)
new_crtc_state = drm_atomic_get_new_crtc_state(
old_state,
new_conn_state->crtc);
else
new_crtc_state = NULL;

if (!crtc_needs_disable(old_crtc_state, new_crtc_state) ||
!drm_atomic_crtc_needs_modeset(old_conn_state->crtc->state))
continue;

Expand Down Expand Up @@ -1023,7 +1054,7 @@ disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state)
if (!drm_atomic_crtc_needs_modeset(new_crtc_state))
continue;

if (!old_crtc_state->active)
if (!crtc_needs_disable(old_crtc_state, new_crtc_state))
continue;

funcs = crtc->helper_private;
Expand Down
4 changes: 4 additions & 0 deletions drivers/gpu/drm/drm_atomic_state_helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ void __drm_atomic_helper_crtc_duplicate_state(struct drm_crtc *crtc,
state->commit = NULL;
state->event = NULL;
state->pageflip_flags = 0;

/* Self refresh should be canceled when a new update is available */
state->active = drm_atomic_crtc_effectively_active(state);
state->self_refresh_active = false;
}
EXPORT_SYMBOL(__drm_atomic_helper_crtc_duplicate_state);

Expand Down
7 changes: 5 additions & 2 deletions drivers/gpu/drm/drm_atomic_uapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ drm_atomic_crtc_get_property(struct drm_crtc *crtc,
struct drm_mode_config *config = &dev->mode_config;

if (property == config->prop_active)
*val = state->active;
*val = drm_atomic_crtc_effectively_active(state);
else if (property == config->prop_mode_id)
*val = (state->mode_blob) ? state->mode_blob->base.id : 0;
else if (property == config->prop_vrr_enabled)
Expand Down Expand Up @@ -788,7 +788,10 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
if (property == config->prop_crtc_id) {
*val = (state->crtc) ? state->crtc->base.id : 0;
} else if (property == config->dpms_property) {
*val = connector->dpms;
if (state->crtc && state->crtc->state->self_refresh_active)
*val = DRM_MODE_DPMS_ON;
else
*val = connector->dpms;
} else if (property == config->tv_select_subconnector_property) {
*val = state->tv.subconnector;
} else if (property == config->tv_left_margin_property) {
Expand Down
216 changes: 216 additions & 0 deletions drivers/gpu/drm/drm_self_refresh_helper.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (C) 2019 Google, Inc.
*
* Authors:
* Sean Paul <seanpaul@chromium.org>
*/
#include <linux/bitops.h>
#include <linux/slab.h>
#include <linux/workqueue.h>

#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_connector.h>
#include <drm/drm_crtc.h>
#include <drm/drm_device.h>
#include <drm/drm_mode_config.h>
#include <drm/drm_modeset_lock.h>
#include <drm/drm_print.h>
#include <drm/drm_self_refresh_helper.h>

/**
* DOC: overview
*
* This helper library provides an easy way for drivers to leverage the atomic
* framework to implement panel self refresh (SR) support. Drivers are
* responsible for initializing and cleaning up the SR helpers on load/unload
* (see &drm_self_refresh_helper_init/&drm_self_refresh_helper_cleanup).
* The connector is responsible for setting
* &drm_connector_state.self_refresh_aware to true at runtime if it is SR-aware
* (meaning it knows how to initiate self refresh on the panel).
*
* Once a crtc has enabled SR using &drm_self_refresh_helper_init, the
* helpers will monitor activity and call back into the driver to enable/disable
* SR as appropriate. The best way to think about this is that it's a DPMS
* on/off request with &drm_crtc_state.self_refresh_active set in crtc state
* that tells you to disable/enable SR on the panel instead of power-cycling it.
*
* During SR, drivers may choose to fully disable their crtc/encoder/bridge
* hardware (in which case no driver changes are necessary), or they can inspect
* &drm_crtc_state.self_refresh_active if they want to enter low power mode
* without full disable (in case full disable/enable is too slow).
*
* SR will be deactivated if there are any atomic updates affecting the
* pipe that is in SR mode. If a crtc is driving multiple connectors, all
* connectors must be SR aware and all will enter/exit SR mode at the same time.
*
* If the crtc and connector are SR aware, but the panel connected does not
* support it (or is otherwise unable to enter SR), the driver should fail
* atomic_check when &drm_crtc_state.self_refresh_active is true.
*/

struct drm_self_refresh_data {
struct drm_crtc *crtc;
struct delayed_work entry_work;
struct drm_atomic_state *save_state;
unsigned int entry_delay_ms;
};

static void drm_self_refresh_helper_entry_work(struct work_struct *work)
{
struct drm_self_refresh_data *sr_data = container_of(
to_delayed_work(work),
struct drm_self_refresh_data, entry_work);
struct drm_crtc *crtc = sr_data->crtc;
struct drm_device *dev = crtc->dev;
struct drm_modeset_acquire_ctx ctx;
struct drm_atomic_state *state;
struct drm_connector *conn;
struct drm_connector_state *conn_state;
struct drm_crtc_state *crtc_state;
int i, ret;

drm_modeset_acquire_init(&ctx, 0);

state = drm_atomic_state_alloc(dev);
if (!state) {
ret = -ENOMEM;
goto out;
}

retry:
state->acquire_ctx = &ctx;

crtc_state = drm_atomic_get_crtc_state(state, crtc);
if (IS_ERR(crtc_state)) {
ret = PTR_ERR(crtc_state);
goto out;
}

if (!crtc_state->enable)
goto out;

ret = drm_atomic_add_affected_connectors(state, crtc);
if (ret)
goto out;

for_each_new_connector_in_state(state, conn, conn_state, i) {
if (!conn_state->self_refresh_aware)
goto out;
}

crtc_state->active = false;
crtc_state->self_refresh_active = true;

ret = drm_atomic_commit(state);
if (ret)
goto out;

out:
if (ret == -EDEADLK) {
drm_atomic_state_clear(state);
ret = drm_modeset_backoff(&ctx);
if (!ret)
goto retry;
}

drm_atomic_state_put(state);
drm_modeset_drop_locks(&ctx);
drm_modeset_acquire_fini(&ctx);
}

/**
* drm_self_refresh_helper_alter_state - Alters the atomic state for SR exit
* @state: the state currently being checked
*
* Called at the end of atomic check. This function checks the state for flags
* incompatible with self refresh exit and changes them. This is a bit
* disingenuous since userspace is expecting one thing and we're giving it
* another. However in order to keep self refresh entirely hidden from
* userspace, this is required.
*
* At the end, we queue up the self refresh entry work so we can enter PSR after
* the desired delay.
*/
void drm_self_refresh_helper_alter_state(struct drm_atomic_state *state)
{
struct drm_crtc *crtc;
struct drm_crtc_state *crtc_state;
int i;

if (state->async_update || !state->allow_modeset) {
for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
if (crtc_state->self_refresh_active) {
state->async_update = false;
state->allow_modeset = true;
break;
}
}
}

for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
struct drm_self_refresh_data *sr_data;

/* Don't trigger the entry timer when we're already in SR */
if (crtc_state->self_refresh_active)
continue;

sr_data = crtc->self_refresh_data;
if (!sr_data)
continue;

mod_delayed_work(system_wq, &sr_data->entry_work,
msecs_to_jiffies(sr_data->entry_delay_ms));
}
}
EXPORT_SYMBOL(drm_self_refresh_helper_alter_state);

/**
* drm_self_refresh_helper_init - Initializes self refresh helpers for a crtc
* @crtc: the crtc which supports self refresh supported displays
* @entry_delay_ms: amount of inactivity to wait before entering self refresh
*
* Returns zero if successful or -errno on failure
*/
int drm_self_refresh_helper_init(struct drm_crtc *crtc,
unsigned int entry_delay_ms)
{
struct drm_self_refresh_data *sr_data = crtc->self_refresh_data;

/* Helper is already initialized */
if (WARN_ON(sr_data))
return -EINVAL;

sr_data = kzalloc(sizeof(*sr_data), GFP_KERNEL);
if (!sr_data)
return -ENOMEM;

INIT_DELAYED_WORK(&sr_data->entry_work,
drm_self_refresh_helper_entry_work);
sr_data->entry_delay_ms = entry_delay_ms;
sr_data->crtc = crtc;

crtc->self_refresh_data = sr_data;
return 0;
}
EXPORT_SYMBOL(drm_self_refresh_helper_init);

/**
* drm_self_refresh_helper_cleanup - Cleans up self refresh helpers for a crtc
* @crtc: the crtc to cleanup
*/
void drm_self_refresh_helper_cleanup(struct drm_crtc *crtc)
{
struct drm_self_refresh_data *sr_data = crtc->self_refresh_data;

/* Helper is already uninitialized */
if (sr_data)
return;

crtc->self_refresh_data = NULL;

cancel_delayed_work_sync(&sr_data->entry_work);
kfree(sr_data);
}
EXPORT_SYMBOL(drm_self_refresh_helper_cleanup);
15 changes: 15 additions & 0 deletions include/drm/drm_atomic.h
Original file line number Diff line number Diff line change
Expand Up @@ -957,4 +957,19 @@ drm_atomic_crtc_needs_modeset(const struct drm_crtc_state *state)
state->connectors_changed;
}

/**
* drm_atomic_crtc_effectively_active - compute whether crtc is actually active
* @state: &drm_crtc_state for the CRTC
*
* When in self refresh mode, the crtc_state->active value will be false, since
* the crtc is off. However in some cases we're interested in whether the crtc
* is active, or effectively active (ie: it's connected to an active display).
* In these cases, use this function instead of just checking active.
*/
static inline bool
drm_atomic_crtc_effectively_active(const struct drm_crtc_state *state)
{
return state->active || state->self_refresh_active;
}

#endif /* DRM_ATOMIC_H_ */
14 changes: 14 additions & 0 deletions include/drm/drm_connector.h
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,20 @@ struct drm_connector_state {
/** @tv: TV connector state */
struct drm_tv_connector_state tv;

/**
* @self_refresh_aware:
*
* This tracks whether a connector is aware of the self refresh state.
* It should be set to true for those connector implementations which
* understand the self refresh state. This is needed since the crtc
* registers the self refresh helpers and it doesn't know if the
* connectors downstream have implemented self refresh entry/exit.
*
* Drivers should set this to true in atomic_check if they know how to
* handle self_refresh requests.
*/
bool self_refresh_aware;

/**
* @picture_aspect_ratio: Connector property to control the
* HDMI infoframe aspect ratio setting.
Expand Down

0 comments on commit 1452c25

Please sign in to comment.