diff mbox series

[v2,1/5] drm: Add helpers to kick off self refresh mode in drivers

Message ID 20190326204509.96515-1-sean@poorly.run (mailing list archive)
State New, archived
Headers show
Series [v2,1/5] drm: Add helpers to kick off self refresh mode in drivers | expand

Commit Message

Sean Paul March 26, 2019, 8:44 p.m. UTC
From: Sean Paul <seanpaul@chromium.org>

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' and/or
'self_refresh_changed' state in crtc_state and connector_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)

Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
Signed-off-by: Sean Paul <seanpaul@chromium.org>
---
 Documentation/gpu/drm-kms-helpers.rst     |   9 +
 drivers/gpu/drm/Makefile                  |   3 +-
 drivers/gpu/drm/drm_atomic.c              |   4 +
 drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
 drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
 drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
 drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
 include/drm/drm_atomic.h                  |  15 ++
 include/drm/drm_connector.h               |  31 ++++
 include/drm/drm_crtc.h                    |  19 ++
 include/drm/drm_self_refresh_helper.h     |  23 +++
 11 files changed, 360 insertions(+), 5 deletions(-)
 create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
 create mode 100644 include/drm/drm_self_refresh_helper.h

Comments

Daniel Vetter March 27, 2019, 6:15 p.m. UTC | #1
On Tue, Mar 26, 2019 at 04:44:54PM -0400, Sean Paul wrote:
> From: Sean Paul <seanpaul@chromium.org>
> 
> 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' and/or
> 'self_refresh_changed' state in crtc_state and connector_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)
> 
> Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
> Signed-off-by: Sean Paul <seanpaul@chromium.org>
> ---
>  Documentation/gpu/drm-kms-helpers.rst     |   9 +
>  drivers/gpu/drm/Makefile                  |   3 +-
>  drivers/gpu/drm/drm_atomic.c              |   4 +
>  drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
>  drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
>  drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
>  drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
>  include/drm/drm_atomic.h                  |  15 ++
>  include/drm/drm_connector.h               |  31 ++++
>  include/drm/drm_crtc.h                    |  19 ++
>  include/drm/drm_self_refresh_helper.h     |  23 +++
>  11 files changed, 360 insertions(+), 5 deletions(-)
>  create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
>  create mode 100644 include/drm/drm_self_refresh_helper.h
> 
> diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
> index 58b375e47615..b0b71f73829f 100644
> --- a/Documentation/gpu/drm-kms-helpers.rst
> +++ b/Documentation/gpu/drm-kms-helpers.rst
> @@ -107,6 +107,15 @@ fbdev Helper Functions Reference
>  .. kernel-doc:: drivers/gpu/drm/drm_fb_helper.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:
> +
>  Framebuffer CMA Helper Functions Reference
>  ==========================================
>  
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index e630eccb951c..5b3a1e26ec94 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -38,7 +38,8 @@ drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_dsc.o drm_probe_helper
>  		drm_kms_helper_common.o drm_dp_dual_mode_helper.o \
>  		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_atomic_state_helper.o drm_damage_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
> diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> index 5eb40130fafb..a06fe55b5ebf 100644
> --- a/drivers/gpu/drm/drm_atomic.c
> +++ b/drivers/gpu/drm/drm_atomic.c
> @@ -379,6 +379,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);
> @@ -881,6 +882,9 @@ 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_active=%d\n", state->self_refresh_active);
> +	drm_printf(p, "\tself_refresh_aware=%d\n", state->self_refresh_aware);
> +	drm_printf(p, "\tself_refresh_changed=%d\n", state->self_refresh_changed);
>  
>  	if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
>  		if (state->writeback_job && state->writeback_job->fb)
> diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> index 2453678d1186..c659db105133 100644
> --- a/drivers/gpu/drm/drm_atomic_helper.c
> +++ b/drivers/gpu/drm/drm_atomic_helper.c
> @@ -30,6 +30,7 @@
>  #include <drm/drm_atomic_uapi.h>
>  #include <drm/drm_plane_helper.h>
>  #include <drm/drm_atomic_helper.h>
> +#include <drm/drm_self_refresh_helper.h>
>  #include <drm/drm_writeback.h>
>  #include <drm/drm_damage_helper.h>
>  #include <linux/dma-fence.h>
> @@ -950,10 +951,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)
>  {
> @@ -974,7 +998,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))

I have a really hard time parsing the logic here and in the helper, since
it's also spread around. I think your helper should also include the
needs_modeset check. Plus your comments look a bit strange - e.g. with the
double negation the reason for the old_crtc->active check isn't that we
need to disable if it's active, but that we don't need to disable if it's
not active (you combine with || here, not &&).

But aside from all that, why do you even change stuff here? The self
refresh worker changes ->active to false, which means needs_modeset will
fire, and no change should be needed in the commit code at all.

That's why I suggested the trick with
drm_atomic_crtc_effectively_active(), it allows us to keep the entire
check/commit driver code as-is.

>  			continue;
>  
> @@ -1018,7 +1049,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;
> @@ -2948,6 +2979,7 @@ int drm_atomic_helper_set_config(struct drm_mode_set *set,
>  		return -ENOMEM;
>  
>  	state->acquire_ctx = ctx;
> +
>  	ret = __drm_atomic_helper_set_config(set, state);
>  	if (ret != 0)
>  		goto fail;
> diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
> index 4985384e51f6..ec90c527deed 100644
> --- a/drivers/gpu/drm/drm_atomic_state_helper.c
> +++ b/drivers/gpu/drm/drm_atomic_state_helper.c
> @@ -105,6 +105,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);
>  
> @@ -370,6 +374,10 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
>  
>  	/* Don't copy over a writeback job, they are used only once */
>  	state->writeback_job = NULL;
> +
> +	/* Self refresh should be canceled when a new update is available */
> +	state->self_refresh_changed = state->self_refresh_active;
> +	state->self_refresh_active = false;

Why the duplication in self-refresh tracking? Connectors never have a
different self-refresh state, and you can always look at the right
crtc_state. Duplication just gives us the chance to screw up and get out
of sync (e.g. if the crtc for a connector changes).

>  }
>  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
>  
> diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
> index 4eb81f10bc54..d2085332172b 100644
> --- a/drivers/gpu/drm/drm_atomic_uapi.c
> +++ b/drivers/gpu/drm/drm_atomic_uapi.c
> @@ -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)
> @@ -785,7 +785,8 @@ 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;
> +		*val = state->self_refresh_active ? DRM_MODE_DPMS_ON :
> +			connector->dpms;
>  	} else if (property == config->tv_select_subconnector_property) {
>  		*val = state->tv.subconnector;
>  	} else if (property == config->tv_left_margin_property) {
> diff --git a/drivers/gpu/drm/drm_self_refresh_helper.c b/drivers/gpu/drm/drm_self_refresh_helper.c
> new file mode 100644
> index 000000000000..a3afa031480e
> --- /dev/null
> +++ b/drivers/gpu/drm/drm_self_refresh_helper.c
> @@ -0,0 +1,212 @@
> +/* SPDX-License-Identifier: MIT */
> +/*
> + * Copyright (C) 2019 Google, Inc.
> + *
> + * Authors:
> + * Sean Paul <seanpaul@chromium.org>
> + */
> +#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>
> +#include <linux/bitops.h>
> +#include <linux/slab.h>
> +#include <linux/workqueue.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 registering and unregistering the SR helpers on load/unload.
> + *
> + * Once a crtc has enabled SR, 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 a flag set in
> + * state that tells you to disable/enable SR on the panel instead of power-
> + * cycling it.
> + *
> + * Drivers may choose to fully disable their crtc/encoder/bridge hardware, or
> + * they can use the "self_refresh_active" and "self_refresh_changed" flags in
> + * object state 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 SR mode.
> + */
> +
> +struct drm_self_refresh_state {
> +	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_state *sr_state = container_of(
> +				to_delayed_work(work),
> +				struct drm_self_refresh_state, entry_work);
> +	struct drm_crtc *crtc = sr_state->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;
> +
> +	crtc_state->active = false;
> +	crtc_state->self_refresh_active = true;
> +
> +	for_each_new_connector_in_state(state, conn, conn_state, i) {
> +		if (!conn_state->self_refresh_aware)
> +			goto out;
> +
> +		conn_state->self_refresh_changed = true;
> +		conn_state->self_refresh_active = true;
> +	}

I wonder whether we should reject the timer here before going ahead.
There's ofc still a race, but if we're unlucky and the self fresh work
fires at the same time as userspace does a new atomic commit we could end
up with the following sequence:
1. self-refresh work starts
2. userspace does ioctls, starts to grab some locks
3. self-refresh grabs sets up the acquire ctx
4. atomic ioctl pulls through, does a commit, blocking the self-refresh
work
5. self-refresh work finally gets all the locks, goes ahead and puts the
display into self-refresh.

Not sure how much we care about this really, or whether delaying the timer
from all atomic_check (including TEST_ONLY) is enough to paper over this.

> +
> +	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) {
> +		for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> +			if (crtc_state->self_refresh_active) {
> +				state->async_update = false;
> +				break;
> +			}
> +		}
> +	}
> +	if (!state->allow_modeset) {
> +		for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> +			if (crtc_state->self_refresh_active) {
> +				state->allow_modeset = true;
> +				break;
> +			}
> +		}
> +	}

I think you can flatten the above two to:

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

Plus a comment that this breaks uapi, but who cares:

	/* This breaks uapi, but we want self-refresh to be as transparent
	 * to userspace as possible. Plus the assumption is that with the
	 * display still on, the modeset will take at most a few vblanks,
	 * and that's still ok. */

> +
> +	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
> +		struct drm_self_refresh_state *sr_state;
> +
> +		/* Don't trigger the entry timer when we're already in SR */
> +		if (crtc_state->self_refresh_active)
> +			continue;
> +
> +		sr_state = crtc->self_refresh_state;
> +		mod_delayed_work(system_wq, &sr_state->entry_work,
> +			 msecs_to_jiffies(sr_state->entry_delay_ms));

This bit needs to be in atomic_commit, not atomic check. Or at least it
feels a bit wrong to delay self-refresh if userspace does a few TEST_ONLY
commits.

If you feel like we should do that, then we need a comment here, since
it's clearly against all the "never touch anything outside of the free
standing state structures from atomic_check code" rules.

> +	}
> +}
> +EXPORT_SYMBOL(drm_self_refresh_helper_alter_state);
> +
> +/**
> + * drm_self_refresh_helper_register - Registers 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
> + */
> +int drm_self_refresh_helper_register(struct drm_crtc *crtc,
> +				     unsigned int entry_delay_ms)
> +{
> +	struct drm_self_refresh_state *sr_state = crtc->self_refresh_state;
> +
> +	/* Helper is already registered */
> +	if (WARN_ON(sr_state))
> +		return -EINVAL;
> +
> +	sr_state = kzalloc(sizeof(*sr_state), GFP_KERNEL);
> +	if (!sr_state)
> +		return -ENOMEM;
> +
> +	INIT_DELAYED_WORK(&sr_state->entry_work,
> +			  drm_self_refresh_helper_entry_work);
> +	sr_state->entry_delay_ms = entry_delay_ms;
> +	sr_state->crtc = crtc;
> +
> +	crtc->self_refresh_state = sr_state;
> +	return 0;
> +}
> +EXPORT_SYMBOL(drm_self_refresh_helper_register);
> +
> +/**
> + * drm_self_refresh_helper_unregister - Unregisters self refresh helpers
> + * @crtc: the crtc to unregister
> + */
> +void drm_self_refresh_helper_unregister(struct drm_crtc *crtc)
> +{
> +	struct drm_self_refresh_state *sr_state = crtc->self_refresh_state;
> +
> +	/* Helper is already unregistered */
> +	if (sr_state)
> +		return;
> +
> +	crtc->self_refresh_state = NULL;
> +
> +	cancel_delayed_work_sync(&sr_state->entry_work);
> +	kfree(sr_state);
> +}
> +EXPORT_SYMBOL(drm_self_refresh_helper_unregister);
> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> index 824a5ed4e216..1e9cab1da97a 100644
> --- a/include/drm/drm_atomic.h
> +++ b/include/drm/drm_atomic.h
> @@ -944,4 +944,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_ */
> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> index c8061992d6cb..0ae7e812ec62 100644
> --- a/include/drm/drm_connector.h
> +++ b/include/drm/drm_connector.h
> @@ -501,6 +501,37 @@ struct drm_connector_state {
>  	/** @tv: TV connector state */
>  	struct drm_tv_connector_state tv;
>  
> +	/**
> +	 * @self_refresh_changed:
> +	 *
> +	 * Set true when self refresh status has changed. This is useful for
> +	 * use in encoder/bridge enable where the old state is unavailable to
> +	 * the driver and it needs to know whether the enable transition is a
> +	 * full transition, or if it just needs to exit self refresh mode.

Uh, we just need proper atomic callbacks with all the states available.
Once you have one, you can get at the others.

> +	 */
> +	bool self_refresh_changed;
> +
> +	/**
> +	 * @self_refresh_active:
> +	 *
> +	 * Used by the self refresh (SR) helpers to denote when the display
> +	 * should be self refreshing. If your connector is SR-capable, check
> +	 * this flag in .disable(). If it is true, instead of shutting off the
> +	 * panel, put it into self refreshing mode.
> +	 */
> +	bool self_refresh_active;
> +
> +	/**
> +	 * @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.

Maybe good to clarify whether drivers can adjust this at runtime from
their atomic_check code? I think they can ...

> +	 */
> +	bool self_refresh_aware;
> +
>  	/**
>  	 * @picture_aspect_ratio: Connector property to control the
>  	 * HDMI infoframe aspect ratio setting.
> diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
> index f7c3022dbdf4..208d68129b4c 100644
> --- a/include/drm/drm_crtc.h
> +++ b/include/drm/drm_crtc.h
> @@ -53,6 +53,7 @@ struct drm_mode_set;
>  struct drm_file;
>  struct drm_clip_rect;
>  struct drm_printer;
> +struct drm_self_refresh_state;
>  struct device_node;
>  struct dma_fence;
>  struct edid;
> @@ -299,6 +300,17 @@ struct drm_crtc_state {
>  	 */
>  	bool vrr_enabled;
>  
> +	/**
> +	 * @self_refresh_active:
> +	 *
> +	 * Used by the self refresh helpers to denote when a self refresh
> +	 * transition is occuring. This will be set on enable/disable callbacks
> +	 * when self refresh is being enabled or disabled. In some cases, it may
> +	 * not be desirable to fully shut off the crtc during self refresh.
> +	 * CRTC's can inspect this flag and determine the best course of action.
> +	 */
> +	bool self_refresh_active;
> +
>  	/**
>  	 * @event:
>  	 *
> @@ -1087,6 +1099,13 @@ struct drm_crtc {
>  	 * The name of the CRTC's fence timeline.
>  	 */
>  	char timeline_name[32];
> +
> +	/**
> +	 * @self_refresh_state: Holds the state for the self refresh helpers
> +	 *
> +	 * Initialized via drm_self_refresh_helper_register().
> +	 */
> +	struct drm_self_refresh_state *self_refresh_state;

Can we drop the _state here? In atomic structures with _state suffix have
very specific meaning&semantics, this isn't one of them. s/state/date/ is
imo clearer in the description too.

>  };
>  
>  /**
> diff --git a/include/drm/drm_self_refresh_helper.h b/include/drm/drm_self_refresh_helper.h
> new file mode 100644
> index 000000000000..015dacb8a807
> --- /dev/null
> +++ b/include/drm/drm_self_refresh_helper.h
> @@ -0,0 +1,23 @@
> +/* SPDX-License-Identifier: MIT */
> +/*
> + * Copyright (C) 2019 Google, Inc.
> + *
> + * Authors:
> + * Sean Paul <seanpaul@chromium.org>
> + */
> +#ifndef DRM_SELF_REFRESH_HELPER_H_
> +#define DRM_SELF_REFRESH_HELPER_H_
> +
> +struct drm_atomic_state;
> +struct drm_connector;
> +struct drm_device;
> +struct drm_self_refresh_state;
> +struct drm_modeset_acquire_ctx;
> +
> +void drm_self_refresh_helper_alter_state(struct drm_atomic_state *state);
> +
> +int drm_self_refresh_helper_register(struct drm_crtc *crtc,
> +				     unsigned int entry_delay_ms);
> +
> +void drm_self_refresh_helper_unregister(struct drm_crtc *crtc);
> +#endif
> -- 
> Sean Paul, Software Engineer, Google / Chromium OS

Aside from the bikesheds and suggestions, looks all good to me. I think
it'd be good to also have an ack from some other soc display maintainer
that this is useful (Tomi, Laurent, Liviu, or someone else). It's always
good to check a design against a handful of actual drivers instead of some
moonshot clean sheet design :-)

But there's a big one: Would be really lovely to have selftests for this
stuff, kinda as a start for making sure we have a solid model of the
atomic helpers ...

Cheers, Daniel
Dan Carpenter March 28, 2019, 2:42 p.m. UTC | #2
Hi Sean,

url:    https://github.com/0day-ci/linux/commits/Sean-Paul/drm-Add-helpers-to-kick-off-self-refresh-mode-in-drivers/20190327-194853

smatch warnings:
drivers/gpu/drm/drm_self_refresh_helper.c:105 drm_self_refresh_helper_entry_work() error: uninitialized symbol 'ret'.

# https://github.com/0day-ci/linux/commit/f5a7344f61b9f1ff99802d4daa7fecbdf0040b42
git remote add linux-review https://github.com/0day-ci/linux
git remote update linux-review
git checkout f5a7344f61b9f1ff99802d4daa7fecbdf0040b42
vim +/ret +105 drivers/gpu/drm/drm_self_refresh_helper.c

f5a7344f Sean Paul 2019-03-26   50  
f5a7344f Sean Paul 2019-03-26   51  static void drm_self_refresh_helper_entry_work(struct work_struct *work)
f5a7344f Sean Paul 2019-03-26   52  {
f5a7344f Sean Paul 2019-03-26   53  	struct drm_self_refresh_state *sr_state = container_of(
f5a7344f Sean Paul 2019-03-26   54  				to_delayed_work(work),
f5a7344f Sean Paul 2019-03-26   55  				struct drm_self_refresh_state, entry_work);
f5a7344f Sean Paul 2019-03-26   56  	struct drm_crtc *crtc = sr_state->crtc;
f5a7344f Sean Paul 2019-03-26   57  	struct drm_device *dev = crtc->dev;
f5a7344f Sean Paul 2019-03-26   58  	struct drm_modeset_acquire_ctx ctx;
f5a7344f Sean Paul 2019-03-26   59  	struct drm_atomic_state *state;
f5a7344f Sean Paul 2019-03-26   60  	struct drm_connector *conn;
f5a7344f Sean Paul 2019-03-26   61  	struct drm_connector_state *conn_state;
f5a7344f Sean Paul 2019-03-26   62  	struct drm_crtc_state *crtc_state;
f5a7344f Sean Paul 2019-03-26   63  	int i, ret;
f5a7344f Sean Paul 2019-03-26   64  
f5a7344f Sean Paul 2019-03-26   65  	drm_modeset_acquire_init(&ctx, 0);
f5a7344f Sean Paul 2019-03-26   66  
f5a7344f Sean Paul 2019-03-26   67  	state = drm_atomic_state_alloc(dev);
f5a7344f Sean Paul 2019-03-26   68  	if (!state) {
f5a7344f Sean Paul 2019-03-26   69  		ret = -ENOMEM;
f5a7344f Sean Paul 2019-03-26   70  		goto out;
f5a7344f Sean Paul 2019-03-26   71  	}
f5a7344f Sean Paul 2019-03-26   72  
f5a7344f Sean Paul 2019-03-26   73  retry:
f5a7344f Sean Paul 2019-03-26   74  	state->acquire_ctx = &ctx;
f5a7344f Sean Paul 2019-03-26   75  
f5a7344f Sean Paul 2019-03-26   76  	crtc_state = drm_atomic_get_crtc_state(state, crtc);
f5a7344f Sean Paul 2019-03-26   77  	if (IS_ERR(crtc_state)) {
f5a7344f Sean Paul 2019-03-26   78  		ret = PTR_ERR(crtc_state);
f5a7344f Sean Paul 2019-03-26   79  		goto out;
f5a7344f Sean Paul 2019-03-26   80  	}
f5a7344f Sean Paul 2019-03-26   81  
f5a7344f Sean Paul 2019-03-26   82  	if (!crtc_state->enable)
f5a7344f Sean Paul 2019-03-26   83  		goto out;
                                                ^^^^^^^^
"ret" not set.

f5a7344f Sean Paul 2019-03-26   84  
f5a7344f Sean Paul 2019-03-26   85  	ret = drm_atomic_add_affected_connectors(state, crtc);
f5a7344f Sean Paul 2019-03-26   86  	if (ret)
f5a7344f Sean Paul 2019-03-26   87  		goto out;
f5a7344f Sean Paul 2019-03-26   88  
f5a7344f Sean Paul 2019-03-26   89  	crtc_state->active = false;
f5a7344f Sean Paul 2019-03-26   90  	crtc_state->self_refresh_active = true;
f5a7344f Sean Paul 2019-03-26   91  
f5a7344f Sean Paul 2019-03-26   92  	for_each_new_connector_in_state(state, conn, conn_state, i) {
f5a7344f Sean Paul 2019-03-26   93  		if (!conn_state->self_refresh_aware)
f5a7344f Sean Paul 2019-03-26   94  			goto out;
f5a7344f Sean Paul 2019-03-26   95  
f5a7344f Sean Paul 2019-03-26   96  		conn_state->self_refresh_changed = true;
f5a7344f Sean Paul 2019-03-26   97  		conn_state->self_refresh_active = true;
f5a7344f Sean Paul 2019-03-26   98  	}
f5a7344f Sean Paul 2019-03-26   99  
f5a7344f Sean Paul 2019-03-26  100  	ret = drm_atomic_commit(state);
f5a7344f Sean Paul 2019-03-26  101  	if (ret)
f5a7344f Sean Paul 2019-03-26  102  		goto out;
f5a7344f Sean Paul 2019-03-26  103  
f5a7344f Sean Paul 2019-03-26  104  out:
f5a7344f Sean Paul 2019-03-26 @105  	if (ret == -EDEADLK) {
f5a7344f Sean Paul 2019-03-26  106  		drm_atomic_state_clear(state);
f5a7344f Sean Paul 2019-03-26  107  		ret = drm_modeset_backoff(&ctx);
f5a7344f Sean Paul 2019-03-26  108  		if (!ret)
f5a7344f Sean Paul 2019-03-26  109  			goto retry;
f5a7344f Sean Paul 2019-03-26  110  	}
f5a7344f Sean Paul 2019-03-26  111  
f5a7344f Sean Paul 2019-03-26  112  	drm_atomic_state_put(state);
f5a7344f Sean Paul 2019-03-26  113  	drm_modeset_drop_locks(&ctx);
f5a7344f Sean Paul 2019-03-26  114  	drm_modeset_acquire_fini(&ctx);
f5a7344f Sean Paul 2019-03-26  115  }
f5a7344f Sean Paul 2019-03-26  116  

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
Sean Paul March 28, 2019, 9:03 p.m. UTC | #3
On Wed, Mar 27, 2019 at 07:15:00PM +0100, Daniel Vetter wrote:
> On Tue, Mar 26, 2019 at 04:44:54PM -0400, Sean Paul wrote:
> > From: Sean Paul <seanpaul@chromium.org>
> > 
> > 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' and/or
> > 'self_refresh_changed' state in crtc_state and connector_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)
> > 
> > Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
> > Signed-off-by: Sean Paul <seanpaul@chromium.org>
> > ---
> >  Documentation/gpu/drm-kms-helpers.rst     |   9 +
> >  drivers/gpu/drm/Makefile                  |   3 +-
> >  drivers/gpu/drm/drm_atomic.c              |   4 +
> >  drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
> >  drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
> >  drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
> >  drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
> >  include/drm/drm_atomic.h                  |  15 ++
> >  include/drm/drm_connector.h               |  31 ++++
> >  include/drm/drm_crtc.h                    |  19 ++
> >  include/drm/drm_self_refresh_helper.h     |  23 +++
> >  11 files changed, 360 insertions(+), 5 deletions(-)
> >  create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
> >  create mode 100644 include/drm/drm_self_refresh_helper.h
> > 

/snip

Thanks for the review!


> > diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> > index 2453678d1186..c659db105133 100644
> > --- a/drivers/gpu/drm/drm_atomic_helper.c
> > +++ b/drivers/gpu/drm/drm_atomic_helper.c
> > @@ -30,6 +30,7 @@
> >  #include <drm/drm_atomic_uapi.h>
> >  #include <drm/drm_plane_helper.h>
> >  #include <drm/drm_atomic_helper.h>
> > +#include <drm/drm_self_refresh_helper.h>
> >  #include <drm/drm_writeback.h>
> >  #include <drm/drm_damage_helper.h>
> >  #include <linux/dma-fence.h>
> > @@ -950,10 +951,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)
> >  {
> > @@ -974,7 +998,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))
> 
> I have a really hard time parsing the logic here and in the helper, since
> it's also spread around. I think your helper should also include the
> needs_modeset check. Plus your comments look a bit strange - e.g. with the
> double negation the reason for the old_crtc->active check isn't that we
> need to disable if it's active, but that we don't need to disable if it's
> not active (you combine with || here, not &&).
> 
> But aside from all that, why do you even change stuff here? The self
> refresh worker changes ->active to false, which means needs_modeset will
> fire, and no change should be needed in the commit code at all.

The issue is that we need to go through disable if we're currently in SR and
need to turn off (ie: old->active=false old->self_refresh_active=true
new->active=false). In this case, needs_modeset returns false.

So yeah, I think the new->self_refresh_active check above is probably redundant
(will need to double check that), but we do need to ensure that we go through a
full disable if we're in SR.

On the enable side, you're right, we don't need to change anything.

> 
> That's why I suggested the trick with
> drm_atomic_crtc_effectively_active(), it allows us to keep the entire
> check/commit driver code as-is.
> 
> >  			continue;
> >  
> > @@ -1018,7 +1049,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;
> > @@ -2948,6 +2979,7 @@ int drm_atomic_helper_set_config(struct drm_mode_set *set,
> >  		return -ENOMEM;
> >  
> >  	state->acquire_ctx = ctx;
> > +
> >  	ret = __drm_atomic_helper_set_config(set, state);
> >  	if (ret != 0)
> >  		goto fail;
> > diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
> > index 4985384e51f6..ec90c527deed 100644
> > --- a/drivers/gpu/drm/drm_atomic_state_helper.c
> > +++ b/drivers/gpu/drm/drm_atomic_state_helper.c
> > @@ -105,6 +105,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);
> >  
> > @@ -370,6 +374,10 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> >  
> >  	/* Don't copy over a writeback job, they are used only once */
> >  	state->writeback_job = NULL;
> > +
> > +	/* Self refresh should be canceled when a new update is available */
> > +	state->self_refresh_changed = state->self_refresh_active;
> > +	state->self_refresh_active = false;
> 
> Why the duplication in self-refresh tracking? Connectors never have a
> different self-refresh state, and you can always look at the right
> crtc_state. Duplication just gives us the chance to screw up and get out
> of sync (e.g. if the crtc for a connector changes).
> 

On disable the crtc is cleared from connector_state, so we don't have access to
it. If I add the appropriate atomic_enable/disable hooks as suggested below, we
should be able to nuke these.


> >  }
> >  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
> >  
> > diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
> > index 4eb81f10bc54..d2085332172b 100644

/snip

> > +
> > +static void drm_self_refresh_helper_entry_work(struct work_struct *work)
> > +{
> > +	struct drm_self_refresh_state *sr_state = container_of(
> > +				to_delayed_work(work),
> > +				struct drm_self_refresh_state, entry_work);
> > +	struct drm_crtc *crtc = sr_state->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;
> > +
> > +	crtc_state->active = false;
> > +	crtc_state->self_refresh_active = true;
> > +
> > +	for_each_new_connector_in_state(state, conn, conn_state, i) {
> > +		if (!conn_state->self_refresh_aware)
> > +			goto out;
> > +
> > +		conn_state->self_refresh_changed = true;
> > +		conn_state->self_refresh_active = true;
> > +	}
> 
> I wonder whether we should reject the timer here before going ahead.
> There's ofc still a race, but if we're unlucky and the self fresh work
> fires at the same time as userspace does a new atomic commit we could end
> up with the following sequence:
> 1. self-refresh work starts
> 2. userspace does ioctls, starts to grab some locks
> 3. self-refresh grabs sets up the acquire ctx
> 4. atomic ioctl pulls through, does a commit, blocking the self-refresh
> work
> 5. self-refresh work finally gets all the locks, goes ahead and puts the
> display into self-refresh.
> 
> Not sure how much we care about this really, or whether delaying the timer
> from all atomic_check (including TEST_ONLY) is enough to paper over this.
>

Hmm, yeah I suppose if the entry timer period was the same as some periodic
animation (like a fancy blinking cursor), you could get some nasty jank. 

As you say it's not avoidable and the bulk of the time here will be doing the
actual entry instead of the setup. So if there is an unfortunate collision,
we're going to hit it more often than not even with the timer check. So I'm
having a hard time getting excited about this.

That said, I'll write the code to check the timer and see how it looks :)

> > +
> > +	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) {
> > +		for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> > +			if (crtc_state->self_refresh_active) {
> > +				state->async_update = false;
> > +				break;
> > +			}
> > +		}
> > +	}
> > +	if (!state->allow_modeset) {
> > +		for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> > +			if (crtc_state->self_refresh_active) {
> > +				state->allow_modeset = true;
> > +				break;
> > +			}
> > +		}
> > +	}
> 
> I think you can flatten the above two to:
> 
> 	for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> 		if (crtc_state->self_refresh_active) {
> 			state->allow_modeset = true;
> 			state->async_update = false;
> 			break;
> 		}
> 	}

D'oh! I've stared at this SR code for sooo long, I guess I got highway hypnosis
on this bit.

> 
> Plus a comment that this breaks uapi, but who cares:
> 
> 	/* This breaks uapi, but we want self-refresh to be as transparent
> 	 * to userspace as possible. Plus the assumption is that with the
> 	 * display still on, the modeset will take at most a few vblanks,
> 	 * and that's still ok. */
> 
> > +
> > +	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
> > +		struct drm_self_refresh_state *sr_state;
> > +
> > +		/* Don't trigger the entry timer when we're already in SR */
> > +		if (crtc_state->self_refresh_active)
> > +			continue;
> > +
> > +		sr_state = crtc->self_refresh_state;
> > +		mod_delayed_work(system_wq, &sr_state->entry_work,
> > +			 msecs_to_jiffies(sr_state->entry_delay_ms));
> 
> This bit needs to be in atomic_commit, not atomic check. Or at least it
> feels a bit wrong to delay self-refresh if userspace does a few TEST_ONLY
> commits.

IMO we should be erring on the side of being overly cautious about entering PSR
since it's a pretty big commitment. Doing this in atomic check is a signal that
userspace is at least interested in poking at KMS and we should back off until
its done.

> 
> If you feel like we should do that, then we need a comment here, since
> it's clearly against all the "never touch anything outside of the free
> standing state structures from atomic_check code" rules.

Good point, I'll add a comment to this effect. We're breaking all the rules
'round here :)

> 
> > +	}
> > +}
> > +EXPORT_SYMBOL(drm_self_refresh_helper_alter_state);

/snip

> > +/**
> > + * 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_ */
> > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> > index c8061992d6cb..0ae7e812ec62 100644
> > --- a/include/drm/drm_connector.h
> > +++ b/include/drm/drm_connector.h
> > @@ -501,6 +501,37 @@ struct drm_connector_state {
> >  	/** @tv: TV connector state */
> >  	struct drm_tv_connector_state tv;
> >  
> > +	/**
> > +	 * @self_refresh_changed:
> > +	 *
> > +	 * Set true when self refresh status has changed. This is useful for
> > +	 * use in encoder/bridge enable where the old state is unavailable to
> > +	 * the driver and it needs to know whether the enable transition is a
> > +	 * full transition, or if it just needs to exit self refresh mode.
> 
> Uh, we just need proper atomic callbacks with all the states available.
> Once you have one, you can get at the others.
> 

Well, sure, we could do that too :)

> > +	 */
> > +	bool self_refresh_changed;
> > +
> > +	/**
> > +	 * @self_refresh_active:
> > +	 *
> > +	 * Used by the self refresh (SR) helpers to denote when the display
> > +	 * should be self refreshing. If your connector is SR-capable, check
> > +	 * this flag in .disable(). If it is true, instead of shutting off the
> > +	 * panel, put it into self refreshing mode.
> > +	 */
> > +	bool self_refresh_active;
> > +
> > +	/**
> > +	 * @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.
> 
> Maybe good to clarify whether drivers can adjust this at runtime from
> their atomic_check code? I think they can ...

Will do. Yes they can, that's exactly what this is for. Since not all panels
connected to a device can support SR, and you could conceivably hotplug between
the two, we want a way to avoid turning off the circuitry upstream of the panel.

> 
> > +	 */
> > +	bool self_refresh_aware;
> > +
> >  	/**
> >  	 * @picture_aspect_ratio: Connector property to control the
> >  	 * HDMI infoframe aspect ratio setting.
> > diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
> > index f7c3022dbdf4..208d68129b4c 100644
> > --- a/include/drm/drm_crtc.h
> > +++ b/include/drm/drm_crtc.h
> > @@ -53,6 +53,7 @@ struct drm_mode_set;
> >  struct drm_file;
> >  struct drm_clip_rect;
> >  struct drm_printer;
> > +struct drm_self_refresh_state;
> >  struct device_node;
> >  struct dma_fence;
> >  struct edid;
> > @@ -299,6 +300,17 @@ struct drm_crtc_state {
> >  	 */
> >  	bool vrr_enabled;
> >  
> > +	/**
> > +	 * @self_refresh_active:
> > +	 *
> > +	 * Used by the self refresh helpers to denote when a self refresh
> > +	 * transition is occuring. This will be set on enable/disable callbacks
> > +	 * when self refresh is being enabled or disabled. In some cases, it may
> > +	 * not be desirable to fully shut off the crtc during self refresh.
> > +	 * CRTC's can inspect this flag and determine the best course of action.
> > +	 */
> > +	bool self_refresh_active;
> > +
> >  	/**
> >  	 * @event:
> >  	 *
> > @@ -1087,6 +1099,13 @@ struct drm_crtc {
> >  	 * The name of the CRTC's fence timeline.
> >  	 */
> >  	char timeline_name[32];
> > +
> > +	/**
> > +	 * @self_refresh_state: Holds the state for the self refresh helpers
> > +	 *
> > +	 * Initialized via drm_self_refresh_helper_register().
> > +	 */
> > +	struct drm_self_refresh_state *self_refresh_state;
> 
> Can we drop the _state here? In atomic structures with _state suffix have
> very specific meaning&semantics, this isn't one of them. s/state/date/ is

data might be even better ;)

> imo clearer in the description too.

Yeah, good suggestion.

> 
> >  };
> >  
> >  /**
> > diff --git a/include/drm/drm_self_refresh_helper.h b/include/drm/drm_self_refresh_helper.h
> > new file mode 100644
> > index 000000000000..015dacb8a807
> > --- /dev/null
> > +++ b/include/drm/drm_self_refresh_helper.h
> > @@ -0,0 +1,23 @@
> > +/* SPDX-License-Identifier: MIT */
> > +/*
> > + * Copyright (C) 2019 Google, Inc.
> > + *
> > + * Authors:
> > + * Sean Paul <seanpaul@chromium.org>
> > + */
> > +#ifndef DRM_SELF_REFRESH_HELPER_H_
> > +#define DRM_SELF_REFRESH_HELPER_H_
> > +
> > +struct drm_atomic_state;
> > +struct drm_connector;
> > +struct drm_device;
> > +struct drm_self_refresh_state;
> > +struct drm_modeset_acquire_ctx;
> > +
> > +void drm_self_refresh_helper_alter_state(struct drm_atomic_state *state);
> > +
> > +int drm_self_refresh_helper_register(struct drm_crtc *crtc,
> > +				     unsigned int entry_delay_ms);
> > +
> > +void drm_self_refresh_helper_unregister(struct drm_crtc *crtc);
> > +#endif
> > -- 
> > Sean Paul, Software Engineer, Google / Chromium OS
> 
> Aside from the bikesheds and suggestions, looks all good to me. I think
> it'd be good to also have an ack from some other soc display maintainer
> that this is useful (Tomi, Laurent, Liviu, or someone else). It's always
> good to check a design against a handful of actual drivers instead of some
> moonshot clean sheet design :-)

Yeah, would be nice to get a third set of eyes. I'm planning on using this
for msm dsi as well, so I've been keeping that in the back of my head while
working on this.

> 
> But there's a big one: Would be really lovely to have selftests for this
> stuff, kinda as a start for making sure we have a solid model of the
> atomic helpers ...

I got so far through the review without any Big Items (well except for the
complete redesign between v1 and v2 (but I kind of expected that))! I've never
done any selftests, so this will be a good chance to get familiar with them.

Thanks again for the review!

Sean

> 
> Cheers, Daniel
> -- 
> Daniel Vetter
> Software Engineer, Intel Corporation
> http://blog.ffwll.ch
Daniel Vetter March 29, 2019, 8:21 a.m. UTC | #4
On Thu, Mar 28, 2019 at 05:03:03PM -0400, Sean Paul wrote:
> On Wed, Mar 27, 2019 at 07:15:00PM +0100, Daniel Vetter wrote:
> > On Tue, Mar 26, 2019 at 04:44:54PM -0400, Sean Paul wrote:
> > > From: Sean Paul <seanpaul@chromium.org>
> > > 
> > > 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' and/or
> > > 'self_refresh_changed' state in crtc_state and connector_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)
> > > 
> > > Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
> > > Signed-off-by: Sean Paul <seanpaul@chromium.org>
> > > ---
> > >  Documentation/gpu/drm-kms-helpers.rst     |   9 +
> > >  drivers/gpu/drm/Makefile                  |   3 +-
> > >  drivers/gpu/drm/drm_atomic.c              |   4 +
> > >  drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
> > >  drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
> > >  drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
> > >  drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
> > >  include/drm/drm_atomic.h                  |  15 ++
> > >  include/drm/drm_connector.h               |  31 ++++
> > >  include/drm/drm_crtc.h                    |  19 ++
> > >  include/drm/drm_self_refresh_helper.h     |  23 +++
> > >  11 files changed, 360 insertions(+), 5 deletions(-)
> > >  create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
> > >  create mode 100644 include/drm/drm_self_refresh_helper.h
> > > 
> 
> /snip
> 
> Thanks for the review!
> 
> 
> > > diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> > > index 2453678d1186..c659db105133 100644
> > > --- a/drivers/gpu/drm/drm_atomic_helper.c
> > > +++ b/drivers/gpu/drm/drm_atomic_helper.c
> > > @@ -30,6 +30,7 @@
> > >  #include <drm/drm_atomic_uapi.h>
> > >  #include <drm/drm_plane_helper.h>
> > >  #include <drm/drm_atomic_helper.h>
> > > +#include <drm/drm_self_refresh_helper.h>
> > >  #include <drm/drm_writeback.h>
> > >  #include <drm/drm_damage_helper.h>
> > >  #include <linux/dma-fence.h>
> > > @@ -950,10 +951,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)
> > >  {
> > > @@ -974,7 +998,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))
> > 
> > I have a really hard time parsing the logic here and in the helper, since
> > it's also spread around. I think your helper should also include the
> > needs_modeset check. Plus your comments look a bit strange - e.g. with the
> > double negation the reason for the old_crtc->active check isn't that we
> > need to disable if it's active, but that we don't need to disable if it's
> > not active (you combine with || here, not &&).
> > 
> > But aside from all that, why do you even change stuff here? The self
> > refresh worker changes ->active to false, which means needs_modeset will
> > fire, and no change should be needed in the commit code at all.
> 
> The issue is that we need to go through disable if we're currently in SR and
> need to turn off (ie: old->active=false old->self_refresh_active=true
> new->active=false). In this case, needs_modeset returns false.
> 
> So yeah, I think the new->self_refresh_active check above is probably redundant
> (will need to double check that), but we do need to ensure that we go through a
> full disable if we're in SR.

Hm right, totally missed that case. Annoying, since it complicates thing
(and makes the code asymetric since we'll never go directly into self
refresh on the enable side).

> On the enable side, you're right, we don't need to change anything.
> 
> > 
> > That's why I suggested the trick with
> > drm_atomic_crtc_effectively_active(), it allows us to keep the entire
> > check/commit driver code as-is.
> > 
> > >  			continue;
> > >  
> > > @@ -1018,7 +1049,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;
> > > @@ -2948,6 +2979,7 @@ int drm_atomic_helper_set_config(struct drm_mode_set *set,
> > >  		return -ENOMEM;
> > >  
> > >  	state->acquire_ctx = ctx;
> > > +
> > >  	ret = __drm_atomic_helper_set_config(set, state);
> > >  	if (ret != 0)
> > >  		goto fail;
> > > diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
> > > index 4985384e51f6..ec90c527deed 100644
> > > --- a/drivers/gpu/drm/drm_atomic_state_helper.c
> > > +++ b/drivers/gpu/drm/drm_atomic_state_helper.c
> > > @@ -105,6 +105,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);
> > >  
> > > @@ -370,6 +374,10 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> > >  
> > >  	/* Don't copy over a writeback job, they are used only once */
> > >  	state->writeback_job = NULL;
> > > +
> > > +	/* Self refresh should be canceled when a new update is available */
> > > +	state->self_refresh_changed = state->self_refresh_active;
> > > +	state->self_refresh_active = false;
> > 
> > Why the duplication in self-refresh tracking? Connectors never have a
> > different self-refresh state, and you can always look at the right
> > crtc_state. Duplication just gives us the chance to screw up and get out
> > of sync (e.g. if the crtc for a connector changes).
> > 
> 
> On disable the crtc is cleared from connector_state, so we don't have access to
> it. If I add the appropriate atomic_enable/disable hooks as suggested below, we
> should be able to nuke these.

Yeah we'd need the old state to look at the crtc and all that. Which is a
lot more trickier.

Since it's such a special case, should we have a dedicated callback for
the direct self-refresh -> completely off transition? It'll be asymetric,
but that's the nature of this I think.

> > >  }
> > >  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
> > >  
> > > diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
> > > index 4eb81f10bc54..d2085332172b 100644
> 
> /snip
> 
> > > +
> > > +static void drm_self_refresh_helper_entry_work(struct work_struct *work)
> > > +{
> > > +	struct drm_self_refresh_state *sr_state = container_of(
> > > +				to_delayed_work(work),
> > > +				struct drm_self_refresh_state, entry_work);
> > > +	struct drm_crtc *crtc = sr_state->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;
> > > +
> > > +	crtc_state->active = false;
> > > +	crtc_state->self_refresh_active = true;
> > > +
> > > +	for_each_new_connector_in_state(state, conn, conn_state, i) {
> > > +		if (!conn_state->self_refresh_aware)
> > > +			goto out;
> > > +
> > > +		conn_state->self_refresh_changed = true;
> > > +		conn_state->self_refresh_active = true;
> > > +	}
> > 
> > I wonder whether we should reject the timer here before going ahead.
> > There's ofc still a race, but if we're unlucky and the self fresh work
> > fires at the same time as userspace does a new atomic commit we could end
> > up with the following sequence:
> > 1. self-refresh work starts
> > 2. userspace does ioctls, starts to grab some locks
> > 3. self-refresh grabs sets up the acquire ctx
> > 4. atomic ioctl pulls through, does a commit, blocking the self-refresh
> > work
> > 5. self-refresh work finally gets all the locks, goes ahead and puts the
> > display into self-refresh.
> > 
> > Not sure how much we care about this really, or whether delaying the timer
> > from all atomic_check (including TEST_ONLY) is enough to paper over this.
> >
> 
> Hmm, yeah I suppose if the entry timer period was the same as some periodic
> animation (like a fancy blinking cursor), you could get some nasty jank. 
> 
> As you say it's not avoidable and the bulk of the time here will be doing the
> actual entry instead of the setup. So if there is an unfortunate collision,
> we're going to hit it more often than not even with the timer check. So I'm
> having a hard time getting excited about this.
> 
> That said, I'll write the code to check the timer and see how it looks :)
> 
> > > +
> > > +	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) {
> > > +		for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> > > +			if (crtc_state->self_refresh_active) {
> > > +				state->async_update = false;
> > > +				break;
> > > +			}
> > > +		}
> > > +	}
> > > +	if (!state->allow_modeset) {
> > > +		for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> > > +			if (crtc_state->self_refresh_active) {
> > > +				state->allow_modeset = true;
> > > +				break;
> > > +			}
> > > +		}
> > > +	}
> > 
> > I think you can flatten the above two to:
> > 
> > 	for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> > 		if (crtc_state->self_refresh_active) {
> > 			state->allow_modeset = true;
> > 			state->async_update = false;
> > 			break;
> > 		}
> > 	}
> 
> D'oh! I've stared at this SR code for sooo long, I guess I got highway hypnosis
> on this bit.
> 
> > 
> > Plus a comment that this breaks uapi, but who cares:
> > 
> > 	/* This breaks uapi, but we want self-refresh to be as transparent
> > 	 * to userspace as possible. Plus the assumption is that with the
> > 	 * display still on, the modeset will take at most a few vblanks,
> > 	 * and that's still ok. */
> > 
> > > +
> > > +	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
> > > +		struct drm_self_refresh_state *sr_state;
> > > +
> > > +		/* Don't trigger the entry timer when we're already in SR */
> > > +		if (crtc_state->self_refresh_active)
> > > +			continue;
> > > +
> > > +		sr_state = crtc->self_refresh_state;
> > > +		mod_delayed_work(system_wq, &sr_state->entry_work,
> > > +			 msecs_to_jiffies(sr_state->entry_delay_ms));
> > 
> > This bit needs to be in atomic_commit, not atomic check. Or at least it
> > feels a bit wrong to delay self-refresh if userspace does a few TEST_ONLY
> > commits.
> 
> IMO we should be erring on the side of being overly cautious about entering PSR
> since it's a pretty big commitment. Doing this in atomic check is a signal that
> userspace is at least interested in poking at KMS and we should back off until
> its done.
> 
> > 
> > If you feel like we should do that, then we need a comment here, since
> > it's clearly against all the "never touch anything outside of the free
> > standing state structures from atomic_check code" rules.
> 
> Good point, I'll add a comment to this effect. We're breaking all the rules
> 'round here :)
> 
> > 
> > > +	}
> > > +}
> > > +EXPORT_SYMBOL(drm_self_refresh_helper_alter_state);
> 
> /snip
> 
> > > +/**
> > > + * 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_ */
> > > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> > > index c8061992d6cb..0ae7e812ec62 100644
> > > --- a/include/drm/drm_connector.h
> > > +++ b/include/drm/drm_connector.h
> > > @@ -501,6 +501,37 @@ struct drm_connector_state {
> > >  	/** @tv: TV connector state */
> > >  	struct drm_tv_connector_state tv;
> > >  
> > > +	/**
> > > +	 * @self_refresh_changed:
> > > +	 *
> > > +	 * Set true when self refresh status has changed. This is useful for
> > > +	 * use in encoder/bridge enable where the old state is unavailable to
> > > +	 * the driver and it needs to know whether the enable transition is a
> > > +	 * full transition, or if it just needs to exit self refresh mode.
> > 
> > Uh, we just need proper atomic callbacks with all the states available.
> > Once you have one, you can get at the others.
> > 
> 
> Well, sure, we could do that too :)

tbh I'm not sure whether that's really better, the duplication just irks
me. With a new callback for the special self-refresh disable (I guess we
only need that on the connector), plus looking at
connector->state->crtc->state->self_refresh, I think we'd be covered
as-is? Or is there a corner case I'm still missing?

> > > +	 */
> > > +	bool self_refresh_changed;
> > > +
> > > +	/**
> > > +	 * @self_refresh_active:
> > > +	 *
> > > +	 * Used by the self refresh (SR) helpers to denote when the display
> > > +	 * should be self refreshing. If your connector is SR-capable, check
> > > +	 * this flag in .disable(). If it is true, instead of shutting off the
> > > +	 * panel, put it into self refreshing mode.
> > > +	 */
> > > +	bool self_refresh_active;
> > > +
> > > +	/**
> > > +	 * @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.
> > 
> > Maybe good to clarify whether drivers can adjust this at runtime from
> > their atomic_check code? I think they can ...
> 
> Will do. Yes they can, that's exactly what this is for. Since not all panels
> connected to a device can support SR, and you could conceivably hotplug between
> the two, we want a way to avoid turning off the circuitry upstream of the panel.
> 
> > 
> > > +	 */
> > > +	bool self_refresh_aware;
> > > +
> > >  	/**
> > >  	 * @picture_aspect_ratio: Connector property to control the
> > >  	 * HDMI infoframe aspect ratio setting.
> > > diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
> > > index f7c3022dbdf4..208d68129b4c 100644
> > > --- a/include/drm/drm_crtc.h
> > > +++ b/include/drm/drm_crtc.h
> > > @@ -53,6 +53,7 @@ struct drm_mode_set;
> > >  struct drm_file;
> > >  struct drm_clip_rect;
> > >  struct drm_printer;
> > > +struct drm_self_refresh_state;
> > >  struct device_node;
> > >  struct dma_fence;
> > >  struct edid;
> > > @@ -299,6 +300,17 @@ struct drm_crtc_state {
> > >  	 */
> > >  	bool vrr_enabled;
> > >  
> > > +	/**
> > > +	 * @self_refresh_active:
> > > +	 *
> > > +	 * Used by the self refresh helpers to denote when a self refresh
> > > +	 * transition is occuring. This will be set on enable/disable callbacks
> > > +	 * when self refresh is being enabled or disabled. In some cases, it may
> > > +	 * not be desirable to fully shut off the crtc during self refresh.
> > > +	 * CRTC's can inspect this flag and determine the best course of action.
> > > +	 */
> > > +	bool self_refresh_active;
> > > +
> > >  	/**
> > >  	 * @event:
> > >  	 *
> > > @@ -1087,6 +1099,13 @@ struct drm_crtc {
> > >  	 * The name of the CRTC's fence timeline.
> > >  	 */
> > >  	char timeline_name[32];
> > > +
> > > +	/**
> > > +	 * @self_refresh_state: Holds the state for the self refresh helpers
> > > +	 *
> > > +	 * Initialized via drm_self_refresh_helper_register().
> > > +	 */
> > > +	struct drm_self_refresh_state *self_refresh_state;
> > 
> > Can we drop the _state here? In atomic structures with _state suffix have
> > very specific meaning&semantics, this isn't one of them. s/state/date/ is
> 
> data might be even better ;)
> 
> > imo clearer in the description too.
> 
> Yeah, good suggestion.
> 
> > 
> > >  };
> > >  
> > >  /**
> > > diff --git a/include/drm/drm_self_refresh_helper.h b/include/drm/drm_self_refresh_helper.h
> > > new file mode 100644
> > > index 000000000000..015dacb8a807
> > > --- /dev/null
> > > +++ b/include/drm/drm_self_refresh_helper.h
> > > @@ -0,0 +1,23 @@
> > > +/* SPDX-License-Identifier: MIT */
> > > +/*
> > > + * Copyright (C) 2019 Google, Inc.
> > > + *
> > > + * Authors:
> > > + * Sean Paul <seanpaul@chromium.org>
> > > + */
> > > +#ifndef DRM_SELF_REFRESH_HELPER_H_
> > > +#define DRM_SELF_REFRESH_HELPER_H_
> > > +
> > > +struct drm_atomic_state;
> > > +struct drm_connector;
> > > +struct drm_device;
> > > +struct drm_self_refresh_state;
> > > +struct drm_modeset_acquire_ctx;
> > > +
> > > +void drm_self_refresh_helper_alter_state(struct drm_atomic_state *state);
> > > +
> > > +int drm_self_refresh_helper_register(struct drm_crtc *crtc,
> > > +				     unsigned int entry_delay_ms);
> > > +
> > > +void drm_self_refresh_helper_unregister(struct drm_crtc *crtc);
> > > +#endif
> > > -- 
> > > Sean Paul, Software Engineer, Google / Chromium OS
> > 
> > Aside from the bikesheds and suggestions, looks all good to me. I think
> > it'd be good to also have an ack from some other soc display maintainer
> > that this is useful (Tomi, Laurent, Liviu, or someone else). It's always
> > good to check a design against a handful of actual drivers instead of some
> > moonshot clean sheet design :-)
> 
> Yeah, would be nice to get a third set of eyes. I'm planning on using this
> for msm dsi as well, so I've been keeping that in the back of my head while
> working on this.
> 
> > 
> > But there's a big one: Would be really lovely to have selftests for this
> > stuff, kinda as a start for making sure we have a solid model of the
> > atomic helpers ...
> 
> I got so far through the review without any Big Items (well except for the
> complete redesign between v1 and v2 (but I kind of expected that))! I've never
> done any selftests, so this will be a good chance to get familiar with them.

Awesome. It might be a bit more work since no one yet mocked enough of
drm_device to run the atomic helpers, but for a basic self-refresh
testcase I don't think you need much really. Plus we've made almost all
callbacks optional, the minimal mock objects should be rather trivial.

Cheers, Daniel
Sean Paul March 29, 2019, 1:16 p.m. UTC | #5
On Fri, Mar 29, 2019 at 09:21:10AM +0100, Daniel Vetter wrote:
> On Thu, Mar 28, 2019 at 05:03:03PM -0400, Sean Paul wrote:
> > On Wed, Mar 27, 2019 at 07:15:00PM +0100, Daniel Vetter wrote:
> > > On Tue, Mar 26, 2019 at 04:44:54PM -0400, Sean Paul wrote:
> > > > From: Sean Paul <seanpaul@chromium.org>
> > > > 
> > > > 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' and/or
> > > > 'self_refresh_changed' state in crtc_state and connector_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)
> > > > 
> > > > Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
> > > > Signed-off-by: Sean Paul <seanpaul@chromium.org>
> > > > ---
> > > >  Documentation/gpu/drm-kms-helpers.rst     |   9 +
> > > >  drivers/gpu/drm/Makefile                  |   3 +-
> > > >  drivers/gpu/drm/drm_atomic.c              |   4 +
> > > >  drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
> > > >  drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
> > > >  drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
> > > >  drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
> > > >  include/drm/drm_atomic.h                  |  15 ++
> > > >  include/drm/drm_connector.h               |  31 ++++
> > > >  include/drm/drm_crtc.h                    |  19 ++
> > > >  include/drm/drm_self_refresh_helper.h     |  23 +++
> > > >  11 files changed, 360 insertions(+), 5 deletions(-)
> > > >  create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
> > > >  create mode 100644 include/drm/drm_self_refresh_helper.h
> > > > 

/snip

> > > > index 4985384e51f6..ec90c527deed 100644
> > > > --- a/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > +++ b/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > @@ -105,6 +105,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);
> > > >  
> > > > @@ -370,6 +374,10 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> > > >  
> > > >  	/* Don't copy over a writeback job, they are used only once */
> > > >  	state->writeback_job = NULL;
> > > > +
> > > > +	/* Self refresh should be canceled when a new update is available */
> > > > +	state->self_refresh_changed = state->self_refresh_active;
> > > > +	state->self_refresh_active = false;
> > > 
> > > Why the duplication in self-refresh tracking? Connectors never have a
> > > different self-refresh state, and you can always look at the right
> > > crtc_state. Duplication just gives us the chance to screw up and get out
> > > of sync (e.g. if the crtc for a connector changes).
> > > 
> > 
> > On disable the crtc is cleared from connector_state, so we don't have access to
> > it. If I add the appropriate atomic_enable/disable hooks as suggested below, we
> > should be able to nuke these.
> 
> Yeah we'd need the old state to look at the crtc and all that. Which is a
> lot more trickier.
> 
> Since it's such a special case, should we have a dedicated callback for
> the direct self-refresh -> completely off transition? It'll be asymetric,
> but that's the nature of this I think.

Right, the asymmetry is really annoying here. If the driver is SR-aware, it makes
sense since SR-active to disable is a real transition. However if the driver is
not SR-aware (ie: it just gets turned off when SR becomes active), the disable
function gets called twice without an enable. So that changes the "for every
enable there is a disable and vice versa" assumption.

This is one of the benefits of the v1 design, SR was bolted on and no existing
rules (async/no_modeset/enable-disable pairs) were [explicitly] broken. That's
not to say it was better, it wasn't, but it was a big consideration.

So, what to do.

I really like the idea that drivers shouldn't have to be SR-aware to be involved
in the pipeline. So if we add a hook for this like you suggest, we could avoid
calling disable twice on anything not SR-aware. We would need to add the hook on
crtc/encoder/bridge to make sure you could mix n' match SR-aware and
non-SR-aware devices.

It probably makes sense to just add matching SR hooks at this point. Since if
the driver is doing something special in disable, it'll need to do something
special in enable. It also reserves enable and disable for what they've
traditionally done. If a device is not SR-aware, it'll just fall back to the
full enable/disable and we'll make sure to not double up on the disable in the
helpers.

So we'll keep symmetry, and avoid having an awful hook name like
disable_from_self_refresh.. yuck!

Thoughts?

> 
> > > >  }
> > > >  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
> > > >  

/snip

> > > > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> > > > index c8061992d6cb..0ae7e812ec62 100644
> > > > --- a/include/drm/drm_connector.h
> > > > +++ b/include/drm/drm_connector.h
> > > > @@ -501,6 +501,37 @@ struct drm_connector_state {
> > > >  	/** @tv: TV connector state */
> > > >  	struct drm_tv_connector_state tv;
> > > >  
> > > > +	/**
> > > > +	 * @self_refresh_changed:
> > > > +	 *
> > > > +	 * Set true when self refresh status has changed. This is useful for
> > > > +	 * use in encoder/bridge enable where the old state is unavailable to
> > > > +	 * the driver and it needs to know whether the enable transition is a
> > > > +	 * full transition, or if it just needs to exit self refresh mode.
> > > 
> > > Uh, we just need proper atomic callbacks with all the states available.
> > > Once you have one, you can get at the others.
> > > 
> > 
> > Well, sure, we could do that too :)
> 
> tbh I'm not sure whether that's really better, the duplication just irks
> me. With a new callback for the special self-refresh disable (I guess we
> only need that on the connector), plus looking at
> connector->state->crtc->state->self_refresh, I think we'd be covered
> as-is? Or is there a corner case I'm still missing?
> 

I think we can remove self_refresh_changed/self_refresh_active if we implement
dedicated hooks for self_refresh_enter/exit. We'll want to keep
self_refresh_aware around since the presence of the callback implementations
does not imply the panel connected supports SR.

As mentioned above, we'll need these hooks on everything in the pipeline to be
fully covered.

> > > > +	 */
> > > > +	bool self_refresh_changed;
> > > > +
> > > > +	/**
> > > > +	 * @self_refresh_active:
> > > > +	 *
> > > > +	 * Used by the self refresh (SR) helpers to denote when the display
> > > > +	 * should be self refreshing. If your connector is SR-capable, check
> > > > +	 * this flag in .disable(). If it is true, instead of shutting off the
> > > > +	 * panel, put it into self refreshing mode.
> > > > +	 */
> > > > +	bool self_refresh_active;
> > > > +
> > > > +	/**
> > > > +	 * @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.
> > > 
> > > Maybe good to clarify whether drivers can adjust this at runtime from
> > > their atomic_check code? I think they can ...
> > 
> > Will do. Yes they can, that's exactly what this is for. Since not all panels
> > connected to a device can support SR, and you could conceivably hotplug between
> > the two, we want a way to avoid turning off the circuitry upstream of the panel.
> > 
> > > 
> > > > +	 */
> > > > +	bool self_refresh_aware;
> > > > +
> > > >  	/**
> > > >  	 * @picture_aspect_ratio: Connector property to control the
> > > >  	 * HDMI infoframe aspect ratio setting.

/snip

> > > 
> > > Aside from the bikesheds and suggestions, looks all good to me. I think
> > > it'd be good to also have an ack from some other soc display maintainer
> > > that this is useful (Tomi, Laurent, Liviu, or someone else). It's always
> > > good to check a design against a handful of actual drivers instead of some
> > > moonshot clean sheet design :-)
> > 
> > Yeah, would be nice to get a third set of eyes. I'm planning on using this
> > for msm dsi as well, so I've been keeping that in the back of my head while
> > working on this.
> > 
> > > 
> > > But there's a big one: Would be really lovely to have selftests for this
> > > stuff, kinda as a start for making sure we have a solid model of the
> > > atomic helpers ...
> > 
> > I got so far through the review without any Big Items (well except for the
> > complete redesign between v1 and v2 (but I kind of expected that))! I've never
> > done any selftests, so this will be a good chance to get familiar with them.
> 
> Awesome. It might be a bit more work since no one yet mocked enough of
> drm_device to run the atomic helpers, but for a basic self-refresh
> testcase I don't think you need much really. Plus we've made almost all
> callbacks optional, the minimal mock objects should be rather trivial.

It'll be a fun experiment :)

> 
> Cheers, Daniel
> -- 
> Daniel Vetter
> Software Engineer, Intel Corporation
> http://blog.ffwll.ch
Daniel Vetter March 29, 2019, 3:36 p.m. UTC | #6
On Fri, Mar 29, 2019 at 09:16:59AM -0400, Sean Paul wrote:
> On Fri, Mar 29, 2019 at 09:21:10AM +0100, Daniel Vetter wrote:
> > On Thu, Mar 28, 2019 at 05:03:03PM -0400, Sean Paul wrote:
> > > On Wed, Mar 27, 2019 at 07:15:00PM +0100, Daniel Vetter wrote:
> > > > On Tue, Mar 26, 2019 at 04:44:54PM -0400, Sean Paul wrote:
> > > > > From: Sean Paul <seanpaul@chromium.org>
> > > > > 
> > > > > 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' and/or
> > > > > 'self_refresh_changed' state in crtc_state and connector_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)
> > > > > 
> > > > > Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
> > > > > Signed-off-by: Sean Paul <seanpaul@chromium.org>
> > > > > ---
> > > > >  Documentation/gpu/drm-kms-helpers.rst     |   9 +
> > > > >  drivers/gpu/drm/Makefile                  |   3 +-
> > > > >  drivers/gpu/drm/drm_atomic.c              |   4 +
> > > > >  drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
> > > > >  drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
> > > > >  drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
> > > > >  drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
> > > > >  include/drm/drm_atomic.h                  |  15 ++
> > > > >  include/drm/drm_connector.h               |  31 ++++
> > > > >  include/drm/drm_crtc.h                    |  19 ++
> > > > >  include/drm/drm_self_refresh_helper.h     |  23 +++
> > > > >  11 files changed, 360 insertions(+), 5 deletions(-)
> > > > >  create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
> > > > >  create mode 100644 include/drm/drm_self_refresh_helper.h
> > > > > 
> 
> /snip
> 
> > > > > index 4985384e51f6..ec90c527deed 100644
> > > > > --- a/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > +++ b/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > @@ -105,6 +105,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);
> > > > >  
> > > > > @@ -370,6 +374,10 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> > > > >  
> > > > >  	/* Don't copy over a writeback job, they are used only once */
> > > > >  	state->writeback_job = NULL;
> > > > > +
> > > > > +	/* Self refresh should be canceled when a new update is available */
> > > > > +	state->self_refresh_changed = state->self_refresh_active;
> > > > > +	state->self_refresh_active = false;
> > > > 
> > > > Why the duplication in self-refresh tracking? Connectors never have a
> > > > different self-refresh state, and you can always look at the right
> > > > crtc_state. Duplication just gives us the chance to screw up and get out
> > > > of sync (e.g. if the crtc for a connector changes).
> > > > 
> > > 
> > > On disable the crtc is cleared from connector_state, so we don't have access to
> > > it. If I add the appropriate atomic_enable/disable hooks as suggested below, we
> > > should be able to nuke these.
> > 
> > Yeah we'd need the old state to look at the crtc and all that. Which is a
> > lot more trickier.
> > 
> > Since it's such a special case, should we have a dedicated callback for
> > the direct self-refresh -> completely off transition? It'll be asymetric,
> > but that's the nature of this I think.
> 
> Right, the asymmetry is really annoying here. If the driver is SR-aware, it makes
> sense since SR-active to disable is a real transition. However if the driver is
> not SR-aware (ie: it just gets turned off when SR becomes active), the disable
> function gets called twice without an enable. So that changes the "for every
> enable there is a disable and vice versa" assumption.
> 
> This is one of the benefits of the v1 design, SR was bolted on and no existing
> rules (async/no_modeset/enable-disable pairs) were [explicitly] broken. That's
> not to say it was better, it wasn't, but it was a big consideration.
> 
> So, what to do.
> 
> I really like the idea that drivers shouldn't have to be SR-aware to be involved
> in the pipeline. So if we add a hook for this like you suggest, we could avoid
> calling disable twice on anything not SR-aware. We would need to add the hook on
> crtc/encoder/bridge to make sure you could mix n' match SR-aware and
> non-SR-aware devices.
> 
> It probably makes sense to just add matching SR hooks at this point. Since if
> the driver is doing something special in disable, it'll need to do something
> special in enable. It also reserves enable and disable for what they've
> traditionally done. If a device is not SR-aware, it'll just fall back to the
> full enable/disable and we'll make sure to not double up on the disable in the
> helpers.
> 
> So we'll keep symmetry, and avoid having an awful hook name like
> disable_from_self_refresh.. yuck!
> 
> Thoughts?

I like the asymetry actually, it has grown on a bit while working out and
pondering this :-)

Benefits:
- we keep the 100% symmetry of enable/disable hooks
- self-refresh aware connector code also gets a bit simpler I think: in
  the normal enable/disable hooks it can just check for
  connector->state->crtc->state->self_refresh_active for sr state changes
  while the pipe is logically staying on
- the one asymmetric case due to this design where we disable the pipe
  harder has an awkward special hook, which gives us a great opportunity
  to explain why it's needed
- nothing changes for non-sr aware drivers
- also no need to duplicate sr state into connectors, since it's all
  fairly explit already in all three state transitions.

- SR on can only happen if the logical crtc_state->active is on and stays on
- SR can get disabled in 2 subcases
  - logical active state stays on -> handled with existing hooks
  - logical active state also goes off -> existing hooks all skip (because
    active=false -> active=false is a no-op), the special ->sr_disable
    takes care

It feels like this is clean, integrates well with atomic helpers overall
and it even makes sense. At least to my slightly oxygen deprived mind
right now ...

> > > > >  }
> > > > >  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
> > > > >  
> 
> /snip
> 
> > > > > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> > > > > index c8061992d6cb..0ae7e812ec62 100644
> > > > > --- a/include/drm/drm_connector.h
> > > > > +++ b/include/drm/drm_connector.h
> > > > > @@ -501,6 +501,37 @@ struct drm_connector_state {
> > > > >  	/** @tv: TV connector state */
> > > > >  	struct drm_tv_connector_state tv;
> > > > >  
> > > > > +	/**
> > > > > +	 * @self_refresh_changed:
> > > > > +	 *
> > > > > +	 * Set true when self refresh status has changed. This is useful for
> > > > > +	 * use in encoder/bridge enable where the old state is unavailable to
> > > > > +	 * the driver and it needs to know whether the enable transition is a
> > > > > +	 * full transition, or if it just needs to exit self refresh mode.
> > > > 
> > > > Uh, we just need proper atomic callbacks with all the states available.
> > > > Once you have one, you can get at the others.
> > > > 
> > > 
> > > Well, sure, we could do that too :)
> > 
> > tbh I'm not sure whether that's really better, the duplication just irks
> > me. With a new callback for the special self-refresh disable (I guess we
> > only need that on the connector), plus looking at
> > connector->state->crtc->state->self_refresh, I think we'd be covered
> > as-is? Or is there a corner case I'm still missing?
> > 
> 
> I think we can remove self_refresh_changed/self_refresh_active if we implement
> dedicated hooks for self_refresh_enter/exit. We'll want to keep
> self_refresh_aware around since the presence of the callback implementations
> does not imply the panel connected supports SR.

Yup, self_refresh_aware is needed.

> As mentioned above, we'll need these hooks on everything in the pipeline to be
> fully covered.

Let's just do the ->sr_disable hook for now. I don't think we need all the
others really.

Cheers, Daniel
Sean Paul March 29, 2019, 6:10 p.m. UTC | #7
On Fri, Mar 29, 2019 at 04:36:32PM +0100, Daniel Vetter wrote:
> On Fri, Mar 29, 2019 at 09:16:59AM -0400, Sean Paul wrote:
> > On Fri, Mar 29, 2019 at 09:21:10AM +0100, Daniel Vetter wrote:
> > > On Thu, Mar 28, 2019 at 05:03:03PM -0400, Sean Paul wrote:
> > > > On Wed, Mar 27, 2019 at 07:15:00PM +0100, Daniel Vetter wrote:
> > > > > On Tue, Mar 26, 2019 at 04:44:54PM -0400, Sean Paul wrote:
> > > > > > From: Sean Paul <seanpaul@chromium.org>
> > > > > > 
> > > > > > 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' and/or
> > > > > > 'self_refresh_changed' state in crtc_state and connector_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)
> > > > > > 
> > > > > > Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
> > > > > > Signed-off-by: Sean Paul <seanpaul@chromium.org>
> > > > > > ---
> > > > > >  Documentation/gpu/drm-kms-helpers.rst     |   9 +
> > > > > >  drivers/gpu/drm/Makefile                  |   3 +-
> > > > > >  drivers/gpu/drm/drm_atomic.c              |   4 +
> > > > > >  drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
> > > > > >  drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
> > > > > >  drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
> > > > > >  drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
> > > > > >  include/drm/drm_atomic.h                  |  15 ++
> > > > > >  include/drm/drm_connector.h               |  31 ++++
> > > > > >  include/drm/drm_crtc.h                    |  19 ++
> > > > > >  include/drm/drm_self_refresh_helper.h     |  23 +++
> > > > > >  11 files changed, 360 insertions(+), 5 deletions(-)
> > > > > >  create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
> > > > > >  create mode 100644 include/drm/drm_self_refresh_helper.h
> > > > > > 
> > 
> > /snip
> > 
> > > > > > index 4985384e51f6..ec90c527deed 100644
> > > > > > --- a/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > > +++ b/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > > @@ -105,6 +105,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);
> > > > > >  
> > > > > > @@ -370,6 +374,10 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> > > > > >  
> > > > > >  	/* Don't copy over a writeback job, they are used only once */
> > > > > >  	state->writeback_job = NULL;
> > > > > > +
> > > > > > +	/* Self refresh should be canceled when a new update is available */
> > > > > > +	state->self_refresh_changed = state->self_refresh_active;
> > > > > > +	state->self_refresh_active = false;
> > > > > 
> > > > > Why the duplication in self-refresh tracking? Connectors never have a
> > > > > different self-refresh state, and you can always look at the right
> > > > > crtc_state. Duplication just gives us the chance to screw up and get out
> > > > > of sync (e.g. if the crtc for a connector changes).
> > > > > 
> > > > 
> > > > On disable the crtc is cleared from connector_state, so we don't have access to
> > > > it. If I add the appropriate atomic_enable/disable hooks as suggested below, we
> > > > should be able to nuke these.
> > > 
> > > Yeah we'd need the old state to look at the crtc and all that. Which is a
> > > lot more trickier.
> > > 
> > > Since it's such a special case, should we have a dedicated callback for
> > > the direct self-refresh -> completely off transition? It'll be asymetric,
> > > but that's the nature of this I think.
> > 
> > Right, the asymmetry is really annoying here. If the driver is SR-aware, it makes
> > sense since SR-active to disable is a real transition. However if the driver is
> > not SR-aware (ie: it just gets turned off when SR becomes active), the disable
> > function gets called twice without an enable. So that changes the "for every
> > enable there is a disable and vice versa" assumption.
> > 
> > This is one of the benefits of the v1 design, SR was bolted on and no existing
> > rules (async/no_modeset/enable-disable pairs) were [explicitly] broken. That's
> > not to say it was better, it wasn't, but it was a big consideration.
> > 
> > So, what to do.
> > 
> > I really like the idea that drivers shouldn't have to be SR-aware to be involved
> > in the pipeline. So if we add a hook for this like you suggest, we could avoid
> > calling disable twice on anything not SR-aware. We would need to add the hook on
> > crtc/encoder/bridge to make sure you could mix n' match SR-aware and
> > non-SR-aware devices.
> > 
> > It probably makes sense to just add matching SR hooks at this point. Since if
> > the driver is doing something special in disable, it'll need to do something
> > special in enable. It also reserves enable and disable for what they've
> > traditionally done. If a device is not SR-aware, it'll just fall back to the
> > full enable/disable and we'll make sure to not double up on the disable in the
> > helpers.
> > 
> > So we'll keep symmetry, and avoid having an awful hook name like
> > disable_from_self_refresh.. yuck!
> > 
> > Thoughts?
> 
> I like the asymetry actually, it has grown on a bit while working out and
> pondering this :-)
> 

I'm not quite there with you, I still think it's better to split it all out.

> Benefits:
> - we keep the 100% symmetry of enable/disable hooks
> - self-refresh aware connector code also gets a bit simpler I think: in
>   the normal enable/disable hooks it can just check for
>   connector->state->crtc->state->self_refresh_active for sr state changes
>   while the pipe is logically staying on
> - the one asymmetric case due to this design where we disable the pipe
>   harder has an awkward special hook, which gives us a great opportunity
>   to explain why it's needed
> - nothing changes for non-sr aware drivers
> - also no need to duplicate sr state into connectors, since it's all
>   fairly explit already in all three state transitions.

To be fair, only one of these is exclusive to asymmetry, and it's the one that
provides the opportunity to add a comment. If the sr functions are symmetric,
the code becomes much more "normal" and less deserving of the explanation.

The reason I would like to split out entry and exit is that it makes the driver 
code a bit easier to rationalize. Currently we need to check the state at the
beginning of enable/disable to determine whether we want the full enable/disable
or the psr exit/enter. So the sr_disable function would really just be plain
old disable without the special casing at the top. In that case, we don't even
need the separate function, we could just limit disable calls only on those
objects which are effectively on (active || sr). That starts sounding a lot like
what we already have here.

Further, doing SR in enable/disable is really just legacy from v1 which tried to
keep as much the same as possible. Now that we're "in it", I think it makes
sense to go all in and make SR a first class citizen.

Sean

> 
> - SR on can only happen if the logical crtc_state->active is on and stays on
> - SR can get disabled in 2 subcases
>   - logical active state stays on -> handled with existing hooks
>   - logical active state also goes off -> existing hooks all skip (because
>     active=false -> active=false is a no-op), the special ->sr_disable
>     takes care
> 
> It feels like this is clean, integrates well with atomic helpers overall
> and it even makes sense. At least to my slightly oxygen deprived mind
> right now ...
> 
> > > > > >  }
> > > > > >  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
> > > > > >  
> > 
> > /snip
> > 
> > > > > > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> > > > > > index c8061992d6cb..0ae7e812ec62 100644
> > > > > > --- a/include/drm/drm_connector.h
> > > > > > +++ b/include/drm/drm_connector.h
> > > > > > @@ -501,6 +501,37 @@ struct drm_connector_state {
> > > > > >  	/** @tv: TV connector state */
> > > > > >  	struct drm_tv_connector_state tv;
> > > > > >  
> > > > > > +	/**
> > > > > > +	 * @self_refresh_changed:
> > > > > > +	 *
> > > > > > +	 * Set true when self refresh status has changed. This is useful for
> > > > > > +	 * use in encoder/bridge enable where the old state is unavailable to
> > > > > > +	 * the driver and it needs to know whether the enable transition is a
> > > > > > +	 * full transition, or if it just needs to exit self refresh mode.
> > > > > 
> > > > > Uh, we just need proper atomic callbacks with all the states available.
> > > > > Once you have one, you can get at the others.
> > > > > 
> > > > 
> > > > Well, sure, we could do that too :)
> > > 
> > > tbh I'm not sure whether that's really better, the duplication just irks
> > > me. With a new callback for the special self-refresh disable (I guess we
> > > only need that on the connector), plus looking at
> > > connector->state->crtc->state->self_refresh, I think we'd be covered
> > > as-is? Or is there a corner case I'm still missing?
> > > 
> > 
> > I think we can remove self_refresh_changed/self_refresh_active if we implement
> > dedicated hooks for self_refresh_enter/exit. We'll want to keep
> > self_refresh_aware around since the presence of the callback implementations
> > does not imply the panel connected supports SR.
> 
> Yup, self_refresh_aware is needed.
> 
> > As mentioned above, we'll need these hooks on everything in the pipeline to be
> > fully covered.
> 
> Let's just do the ->sr_disable hook for now. I don't think we need all the
> others really.
> 
> Cheers, Daniel
> -- 
> Daniel Vetter
> Software Engineer, Intel Corporation
> http://blog.ffwll.ch
Daniel Vetter March 29, 2019, 7:21 p.m. UTC | #8
On Fri, Mar 29, 2019 at 7:10 PM Sean Paul <sean@poorly.run> wrote:
>
> On Fri, Mar 29, 2019 at 04:36:32PM +0100, Daniel Vetter wrote:
> > On Fri, Mar 29, 2019 at 09:16:59AM -0400, Sean Paul wrote:
> > > On Fri, Mar 29, 2019 at 09:21:10AM +0100, Daniel Vetter wrote:
> > > > On Thu, Mar 28, 2019 at 05:03:03PM -0400, Sean Paul wrote:
> > > > > On Wed, Mar 27, 2019 at 07:15:00PM +0100, Daniel Vetter wrote:
> > > > > > On Tue, Mar 26, 2019 at 04:44:54PM -0400, Sean Paul wrote:
> > > > > > > From: Sean Paul <seanpaul@chromium.org>
> > > > > > >
> > > > > > > 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' and/or
> > > > > > > 'self_refresh_changed' state in crtc_state and connector_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)
> > > > > > >
> > > > > > > Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
> > > > > > > Signed-off-by: Sean Paul <seanpaul@chromium.org>
> > > > > > > ---
> > > > > > >  Documentation/gpu/drm-kms-helpers.rst     |   9 +
> > > > > > >  drivers/gpu/drm/Makefile                  |   3 +-
> > > > > > >  drivers/gpu/drm/drm_atomic.c              |   4 +
> > > > > > >  drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
> > > > > > >  drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
> > > > > > >  drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
> > > > > > >  drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
> > > > > > >  include/drm/drm_atomic.h                  |  15 ++
> > > > > > >  include/drm/drm_connector.h               |  31 ++++
> > > > > > >  include/drm/drm_crtc.h                    |  19 ++
> > > > > > >  include/drm/drm_self_refresh_helper.h     |  23 +++
> > > > > > >  11 files changed, 360 insertions(+), 5 deletions(-)
> > > > > > >  create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
> > > > > > >  create mode 100644 include/drm/drm_self_refresh_helper.h
> > > > > > >
> > >
> > > /snip
> > >
> > > > > > > index 4985384e51f6..ec90c527deed 100644
> > > > > > > --- a/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > > > +++ b/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > > > @@ -105,6 +105,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);
> > > > > > >
> > > > > > > @@ -370,6 +374,10 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> > > > > > >
> > > > > > >     /* Don't copy over a writeback job, they are used only once */
> > > > > > >     state->writeback_job = NULL;
> > > > > > > +
> > > > > > > +   /* Self refresh should be canceled when a new update is available */
> > > > > > > +   state->self_refresh_changed = state->self_refresh_active;
> > > > > > > +   state->self_refresh_active = false;
> > > > > >
> > > > > > Why the duplication in self-refresh tracking? Connectors never have a
> > > > > > different self-refresh state, and you can always look at the right
> > > > > > crtc_state. Duplication just gives us the chance to screw up and get out
> > > > > > of sync (e.g. if the crtc for a connector changes).
> > > > > >
> > > > >
> > > > > On disable the crtc is cleared from connector_state, so we don't have access to
> > > > > it. If I add the appropriate atomic_enable/disable hooks as suggested below, we
> > > > > should be able to nuke these.
> > > >
> > > > Yeah we'd need the old state to look at the crtc and all that. Which is a
> > > > lot more trickier.
> > > >
> > > > Since it's such a special case, should we have a dedicated callback for
> > > > the direct self-refresh -> completely off transition? It'll be asymetric,
> > > > but that's the nature of this I think.
> > >
> > > Right, the asymmetry is really annoying here. If the driver is SR-aware, it makes
> > > sense since SR-active to disable is a real transition. However if the driver is
> > > not SR-aware (ie: it just gets turned off when SR becomes active), the disable
> > > function gets called twice without an enable. So that changes the "for every
> > > enable there is a disable and vice versa" assumption.
> > >
> > > This is one of the benefits of the v1 design, SR was bolted on and no existing
> > > rules (async/no_modeset/enable-disable pairs) were [explicitly] broken. That's
> > > not to say it was better, it wasn't, but it was a big consideration.
> > >
> > > So, what to do.
> > >
> > > I really like the idea that drivers shouldn't have to be SR-aware to be involved
> > > in the pipeline. So if we add a hook for this like you suggest, we could avoid
> > > calling disable twice on anything not SR-aware. We would need to add the hook on
> > > crtc/encoder/bridge to make sure you could mix n' match SR-aware and
> > > non-SR-aware devices.
> > >
> > > It probably makes sense to just add matching SR hooks at this point. Since if
> > > the driver is doing something special in disable, it'll need to do something
> > > special in enable. It also reserves enable and disable for what they've
> > > traditionally done. If a device is not SR-aware, it'll just fall back to the
> > > full enable/disable and we'll make sure to not double up on the disable in the
> > > helpers.
> > >
> > > So we'll keep symmetry, and avoid having an awful hook name like
> > > disable_from_self_refresh.. yuck!
> > >
> > > Thoughts?
> >
> > I like the asymetry actually, it has grown on a bit while working out and
> > pondering this :-)
> >
>
> I'm not quite there with you, I still think it's better to split it all out.
>
> > Benefits:
> > - we keep the 100% symmetry of enable/disable hooks
> > - self-refresh aware connector code also gets a bit simpler I think: in
> >   the normal enable/disable hooks it can just check for
> >   connector->state->crtc->state->self_refresh_active for sr state changes
> >   while the pipe is logically staying on
> > - the one asymmetric case due to this design where we disable the pipe
> >   harder has an awkward special hook, which gives us a great opportunity
> >   to explain why it's needed
> > - nothing changes for non-sr aware drivers
> > - also no need to duplicate sr state into connectors, since it's all
> >   fairly explit already in all three state transitions.
>
> To be fair, only one of these is exclusive to asymmetry, and it's the one that
> provides the opportunity to add a comment. If the sr functions are symmetric,
> the code becomes much more "normal" and less deserving of the explanation.
>
> The reason I would like to split out entry and exit is that it makes the driver
> code a bit easier to rationalize. Currently we need to check the state at the
> beginning of enable/disable to determine whether we want the full enable/disable
> or the psr exit/enter. So the sr_disable function would really just be plain
> old disable without the special casing at the top. In that case, we don't even
> need the separate function, we could just limit disable calls only on those
> objects which are effectively on (active || sr). That starts sounding a lot like
> what we already have here.
>
> Further, doing SR in enable/disable is really just legacy from v1 which tried to
> keep as much the same as possible. Now that we're "in it", I think it makes
> sense to go all in and make SR a first class citizen.

Hm, question is: How many hooks do you need? Just something on the
connector, or on the encoder, or everywhere? And how do you handle the
various state transitions. On the disable side we have:
- active on -> active off, no sr (userspace disables crtc)
- active on, sr off -> active ooff, sr on (sr timer fires and suspends crtc)
- active off, sr on -> active off, sr off (userspace disable crtc
while crtc is in sr)
These are all "logical active on" -> "something" transitions where we
disable something (crtc, or display or both)

So in a way you'd need 3 hooks here for the full matrix. And they all
kinda disable something. On the enable side we have:
- active off, sr off -> active on, sr off (userspace enables crtc)
- active off, sr on -> active on, sr off (userspace does a pageflip, stops sr)
Here we either enable the crtc (display already on) or both. Since we
only go into sr with the timer there's no 3rd case of only enabling
the display. So still asymetric, even with lots more hooks.

If you want the full matrix, there's going to be a _lot_ of hooks. I
think slightly more awkward driver, but less hooks is better. Hence
the slightly awkward middle ground of a special disable_from_sr hook.
But maybe there's a better option somewhere else ...
-Daniel

>
> Sean
>
> >
> > - SR on can only happen if the logical crtc_state->active is on and stays on
> > - SR can get disabled in 2 subcases
> >   - logical active state stays on -> handled with existing hooks
> >   - logical active state also goes off -> existing hooks all skip (because
> >     active=false -> active=false is a no-op), the special ->sr_disable
> >     takes care
> >
> > It feels like this is clean, integrates well with atomic helpers overall
> > and it even makes sense. At least to my slightly oxygen deprived mind
> > right now ...
> >
> > > > > > >  }
> > > > > > >  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
> > > > > > >
> > >
> > > /snip
> > >
> > > > > > > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> > > > > > > index c8061992d6cb..0ae7e812ec62 100644
> > > > > > > --- a/include/drm/drm_connector.h
> > > > > > > +++ b/include/drm/drm_connector.h
> > > > > > > @@ -501,6 +501,37 @@ struct drm_connector_state {
> > > > > > >     /** @tv: TV connector state */
> > > > > > >     struct drm_tv_connector_state tv;
> > > > > > >
> > > > > > > +   /**
> > > > > > > +    * @self_refresh_changed:
> > > > > > > +    *
> > > > > > > +    * Set true when self refresh status has changed. This is useful for
> > > > > > > +    * use in encoder/bridge enable where the old state is unavailable to
> > > > > > > +    * the driver and it needs to know whether the enable transition is a
> > > > > > > +    * full transition, or if it just needs to exit self refresh mode.
> > > > > >
> > > > > > Uh, we just need proper atomic callbacks with all the states available.
> > > > > > Once you have one, you can get at the others.
> > > > > >
> > > > >
> > > > > Well, sure, we could do that too :)
> > > >
> > > > tbh I'm not sure whether that's really better, the duplication just irks
> > > > me. With a new callback for the special self-refresh disable (I guess we
> > > > only need that on the connector), plus looking at
> > > > connector->state->crtc->state->self_refresh, I think we'd be covered
> > > > as-is? Or is there a corner case I'm still missing?
> > > >
> > >
> > > I think we can remove self_refresh_changed/self_refresh_active if we implement
> > > dedicated hooks for self_refresh_enter/exit. We'll want to keep
> > > self_refresh_aware around since the presence of the callback implementations
> > > does not imply the panel connected supports SR.
> >
> > Yup, self_refresh_aware is needed.
> >
> > > As mentioned above, we'll need these hooks on everything in the pipeline to be
> > > fully covered.
> >
> > Let's just do the ->sr_disable hook for now. I don't think we need all the
> > others really.
> >
> > Cheers, Daniel
> > --
> > Daniel Vetter
> > Software Engineer, Intel Corporation
> > http://blog.ffwll.ch
>
> --
> Sean Paul, Software Engineer, Google / Chromium OS
Sean Paul April 1, 2019, 1:49 p.m. UTC | #9
On Fri, Mar 29, 2019 at 08:21:31PM +0100, Daniel Vetter wrote:
> On Fri, Mar 29, 2019 at 7:10 PM Sean Paul <sean@poorly.run> wrote:
> >
> > On Fri, Mar 29, 2019 at 04:36:32PM +0100, Daniel Vetter wrote:
> > > On Fri, Mar 29, 2019 at 09:16:59AM -0400, Sean Paul wrote:
> > > > On Fri, Mar 29, 2019 at 09:21:10AM +0100, Daniel Vetter wrote:
> > > > > On Thu, Mar 28, 2019 at 05:03:03PM -0400, Sean Paul wrote:
> > > > > > On Wed, Mar 27, 2019 at 07:15:00PM +0100, Daniel Vetter wrote:
> > > > > > > On Tue, Mar 26, 2019 at 04:44:54PM -0400, Sean Paul wrote:
> > > > > > > > From: Sean Paul <seanpaul@chromium.org>
> > > > > > > >
> > > > > > > > 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' and/or
> > > > > > > > 'self_refresh_changed' state in crtc_state and connector_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)
> > > > > > > >
> > > > > > > > Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
> > > > > > > > Signed-off-by: Sean Paul <seanpaul@chromium.org>
> > > > > > > > ---
> > > > > > > >  Documentation/gpu/drm-kms-helpers.rst     |   9 +
> > > > > > > >  drivers/gpu/drm/Makefile                  |   3 +-
> > > > > > > >  drivers/gpu/drm/drm_atomic.c              |   4 +
> > > > > > > >  drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
> > > > > > > >  drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
> > > > > > > >  drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
> > > > > > > >  drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
> > > > > > > >  include/drm/drm_atomic.h                  |  15 ++
> > > > > > > >  include/drm/drm_connector.h               |  31 ++++
> > > > > > > >  include/drm/drm_crtc.h                    |  19 ++
> > > > > > > >  include/drm/drm_self_refresh_helper.h     |  23 +++
> > > > > > > >  11 files changed, 360 insertions(+), 5 deletions(-)
> > > > > > > >  create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
> > > > > > > >  create mode 100644 include/drm/drm_self_refresh_helper.h
> > > > > > > >
> > > >
> > > > /snip
> > > >
> > > > > > > > index 4985384e51f6..ec90c527deed 100644
> > > > > > > > --- a/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > > > > +++ b/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > > > > @@ -105,6 +105,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);
> > > > > > > >
> > > > > > > > @@ -370,6 +374,10 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> > > > > > > >
> > > > > > > >     /* Don't copy over a writeback job, they are used only once */
> > > > > > > >     state->writeback_job = NULL;
> > > > > > > > +
> > > > > > > > +   /* Self refresh should be canceled when a new update is available */
> > > > > > > > +   state->self_refresh_changed = state->self_refresh_active;
> > > > > > > > +   state->self_refresh_active = false;
> > > > > > >
> > > > > > > Why the duplication in self-refresh tracking? Connectors never have a
> > > > > > > different self-refresh state, and you can always look at the right
> > > > > > > crtc_state. Duplication just gives us the chance to screw up and get out
> > > > > > > of sync (e.g. if the crtc for a connector changes).
> > > > > > >
> > > > > >
> > > > > > On disable the crtc is cleared from connector_state, so we don't have access to
> > > > > > it. If I add the appropriate atomic_enable/disable hooks as suggested below, we
> > > > > > should be able to nuke these.
> > > > >
> > > > > Yeah we'd need the old state to look at the crtc and all that. Which is a
> > > > > lot more trickier.
> > > > >
> > > > > Since it's such a special case, should we have a dedicated callback for
> > > > > the direct self-refresh -> completely off transition? It'll be asymetric,
> > > > > but that's the nature of this I think.
> > > >
> > > > Right, the asymmetry is really annoying here. If the driver is SR-aware, it makes
> > > > sense since SR-active to disable is a real transition. However if the driver is
> > > > not SR-aware (ie: it just gets turned off when SR becomes active), the disable
> > > > function gets called twice without an enable. So that changes the "for every
> > > > enable there is a disable and vice versa" assumption.
> > > >
> > > > This is one of the benefits of the v1 design, SR was bolted on and no existing
> > > > rules (async/no_modeset/enable-disable pairs) were [explicitly] broken. That's
> > > > not to say it was better, it wasn't, but it was a big consideration.
> > > >
> > > > So, what to do.
> > > >
> > > > I really like the idea that drivers shouldn't have to be SR-aware to be involved
> > > > in the pipeline. So if we add a hook for this like you suggest, we could avoid
> > > > calling disable twice on anything not SR-aware. We would need to add the hook on
> > > > crtc/encoder/bridge to make sure you could mix n' match SR-aware and
> > > > non-SR-aware devices.
> > > >
> > > > It probably makes sense to just add matching SR hooks at this point. Since if
> > > > the driver is doing something special in disable, it'll need to do something
> > > > special in enable. It also reserves enable and disable for what they've
> > > > traditionally done. If a device is not SR-aware, it'll just fall back to the
> > > > full enable/disable and we'll make sure to not double up on the disable in the
> > > > helpers.
> > > >
> > > > So we'll keep symmetry, and avoid having an awful hook name like
> > > > disable_from_self_refresh.. yuck!
> > > >
> > > > Thoughts?
> > >
> > > I like the asymetry actually, it has grown on a bit while working out and
> > > pondering this :-)
> > >
> >
> > I'm not quite there with you, I still think it's better to split it all out.
> >
> > > Benefits:
> > > - we keep the 100% symmetry of enable/disable hooks
> > > - self-refresh aware connector code also gets a bit simpler I think: in
> > >   the normal enable/disable hooks it can just check for
> > >   connector->state->crtc->state->self_refresh_active for sr state changes
> > >   while the pipe is logically staying on
> > > - the one asymmetric case due to this design where we disable the pipe
> > >   harder has an awkward special hook, which gives us a great opportunity
> > >   to explain why it's needed
> > > - nothing changes for non-sr aware drivers
> > > - also no need to duplicate sr state into connectors, since it's all
> > >   fairly explit already in all three state transitions.
> >
> > To be fair, only one of these is exclusive to asymmetry, and it's the one that
> > provides the opportunity to add a comment. If the sr functions are symmetric,
> > the code becomes much more "normal" and less deserving of the explanation.
> >
> > The reason I would like to split out entry and exit is that it makes the driver
> > code a bit easier to rationalize. Currently we need to check the state at the
> > beginning of enable/disable to determine whether we want the full enable/disable
> > or the psr exit/enter. So the sr_disable function would really just be plain
> > old disable without the special casing at the top. In that case, we don't even
> > need the separate function, we could just limit disable calls only on those
> > objects which are effectively on (active || sr). That starts sounding a lot like
> > what we already have here.
> >
> > Further, doing SR in enable/disable is really just legacy from v1 which tried to
> > keep as much the same as possible. Now that we're "in it", I think it makes
> > sense to go all in and make SR a first class citizen.
> 
> Hm, question is: How many hooks do you need? Just something on the
> connector, or on the encoder, or everywhere?

bridge/encoder/crtc all do special things during SR transitions, I don't think
connector is necessary. This is the same for any .sr_disable function, everyone
would need to implement it.

> And how do you handle the
> various state transitions. On the disable side we have:
> - active on -> active off, no sr (userspace disables crtc)
> - active on, sr off -> active ooff, sr on (sr timer fires and suspends crtc)
> - active off, sr on -> active off, sr off (userspace disable crtc
> while crtc is in sr)
> These are all "logical active on" -> "something" transitions where we
> disable something (crtc, or display or both)
> 
> So in a way you'd need 3 hooks here for the full matrix.
> And they all
> kinda disable something. On the enable side we have:
> - active off, sr off -> active on, sr off (userspace enables crtc)
> - active off, sr on -> active on, sr off (userspace does a pageflip, stops sr)
> Here we either enable the crtc (display already on) or both. Since we
> only go into sr with the timer there's no 3rd case of only enabling
> the display. So still asymetric, even with lots more hooks.

We don't need the (active off, sr on) -> (active off, sr off) (third) case
above, it's the same as the first. Just doing a full disable is sufficient,
so you would have symmetry in the enable/disable calls and asymmetry in the
sr calls. This is similar to enabling a plane, or turning other HW features on
while enabled. SR is after all just a feature of the hardware.

> 
> If you want the full matrix, there's going to be a _lot_ of hooks. I
> think slightly more awkward driver, but less hooks is better. Hence
> the slightly awkward middle ground of a special disable_from_sr hook.
> But maybe there's a better option somewhere else ...

There's really no reason to even have the sr_disable function. The .disable
function in the driver will already need special casing to detect psr_entry
vs full disable, so it'd be better to just call disable twice. The .sr_disable
function would always just do a full disable (ie: the .disable implementation
without the sr checks at the top).

So the debate should be: add sr_enable/disable pair of hooks, or overload
disable with asymmetry (current implementation).

Sean

> -Daniel
> 
> >
> > Sean
> >
> > >
> > > - SR on can only happen if the logical crtc_state->active is on and stays on
> > > - SR can get disabled in 2 subcases
> > >   - logical active state stays on -> handled with existing hooks
> > >   - logical active state also goes off -> existing hooks all skip (because
> > >     active=false -> active=false is a no-op), the special ->sr_disable
> > >     takes care
> > >
> > > It feels like this is clean, integrates well with atomic helpers overall
> > > and it even makes sense. At least to my slightly oxygen deprived mind
> > > right now ...
> > >
> > > > > > > >  }
> > > > > > > >  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
> > > > > > > >
> > > >
> > > > /snip
> > > >
> > > > > > > > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> > > > > > > > index c8061992d6cb..0ae7e812ec62 100644
> > > > > > > > --- a/include/drm/drm_connector.h
> > > > > > > > +++ b/include/drm/drm_connector.h
> > > > > > > > @@ -501,6 +501,37 @@ struct drm_connector_state {
> > > > > > > >     /** @tv: TV connector state */
> > > > > > > >     struct drm_tv_connector_state tv;
> > > > > > > >
> > > > > > > > +   /**
> > > > > > > > +    * @self_refresh_changed:
> > > > > > > > +    *
> > > > > > > > +    * Set true when self refresh status has changed. This is useful for
> > > > > > > > +    * use in encoder/bridge enable where the old state is unavailable to
> > > > > > > > +    * the driver and it needs to know whether the enable transition is a
> > > > > > > > +    * full transition, or if it just needs to exit self refresh mode.
> > > > > > >
> > > > > > > Uh, we just need proper atomic callbacks with all the states available.
> > > > > > > Once you have one, you can get at the others.
> > > > > > >
> > > > > >
> > > > > > Well, sure, we could do that too :)
> > > > >
> > > > > tbh I'm not sure whether that's really better, the duplication just irks
> > > > > me. With a new callback for the special self-refresh disable (I guess we
> > > > > only need that on the connector), plus looking at
> > > > > connector->state->crtc->state->self_refresh, I think we'd be covered
> > > > > as-is? Or is there a corner case I'm still missing?
> > > > >
> > > >
> > > > I think we can remove self_refresh_changed/self_refresh_active if we implement
> > > > dedicated hooks for self_refresh_enter/exit. We'll want to keep
> > > > self_refresh_aware around since the presence of the callback implementations
> > > > does not imply the panel connected supports SR.
> > >
> > > Yup, self_refresh_aware is needed.
> > >
> > > > As mentioned above, we'll need these hooks on everything in the pipeline to be
> > > > fully covered.
> > >
> > > Let's just do the ->sr_disable hook for now. I don't think we need all the
> > > others really.
> > >
> > > Cheers, Daniel
> > > --
> > > Daniel Vetter
> > > Software Engineer, Intel Corporation
> > > http://blog.ffwll.ch
> >
> > --
> > Sean Paul, Software Engineer, Google / Chromium OS
> 
> 
> 
> -- 
> Daniel Vetter
> Software Engineer, Intel Corporation
> +41 (0) 79 365 57 48 - http://blog.ffwll.ch
Daniel Vetter April 2, 2019, 7:49 a.m. UTC | #10
On Mon, Apr 01, 2019 at 09:49:30AM -0400, Sean Paul wrote:
> On Fri, Mar 29, 2019 at 08:21:31PM +0100, Daniel Vetter wrote:
> > On Fri, Mar 29, 2019 at 7:10 PM Sean Paul <sean@poorly.run> wrote:
> > >
> > > On Fri, Mar 29, 2019 at 04:36:32PM +0100, Daniel Vetter wrote:
> > > > On Fri, Mar 29, 2019 at 09:16:59AM -0400, Sean Paul wrote:
> > > > > On Fri, Mar 29, 2019 at 09:21:10AM +0100, Daniel Vetter wrote:
> > > > > > On Thu, Mar 28, 2019 at 05:03:03PM -0400, Sean Paul wrote:
> > > > > > > On Wed, Mar 27, 2019 at 07:15:00PM +0100, Daniel Vetter wrote:
> > > > > > > > On Tue, Mar 26, 2019 at 04:44:54PM -0400, Sean Paul wrote:
> > > > > > > > > From: Sean Paul <seanpaul@chromium.org>
> > > > > > > > >
> > > > > > > > > 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' and/or
> > > > > > > > > 'self_refresh_changed' state in crtc_state and connector_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)
> > > > > > > > >
> > > > > > > > > Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
> > > > > > > > > Signed-off-by: Sean Paul <seanpaul@chromium.org>
> > > > > > > > > ---
> > > > > > > > >  Documentation/gpu/drm-kms-helpers.rst     |   9 +
> > > > > > > > >  drivers/gpu/drm/Makefile                  |   3 +-
> > > > > > > > >  drivers/gpu/drm/drm_atomic.c              |   4 +
> > > > > > > > >  drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
> > > > > > > > >  drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
> > > > > > > > >  drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
> > > > > > > > >  drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
> > > > > > > > >  include/drm/drm_atomic.h                  |  15 ++
> > > > > > > > >  include/drm/drm_connector.h               |  31 ++++
> > > > > > > > >  include/drm/drm_crtc.h                    |  19 ++
> > > > > > > > >  include/drm/drm_self_refresh_helper.h     |  23 +++
> > > > > > > > >  11 files changed, 360 insertions(+), 5 deletions(-)
> > > > > > > > >  create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
> > > > > > > > >  create mode 100644 include/drm/drm_self_refresh_helper.h
> > > > > > > > >
> > > > >
> > > > > /snip
> > > > >
> > > > > > > > > index 4985384e51f6..ec90c527deed 100644
> > > > > > > > > --- a/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > > > > > +++ b/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > > > > > @@ -105,6 +105,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);
> > > > > > > > >
> > > > > > > > > @@ -370,6 +374,10 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> > > > > > > > >
> > > > > > > > >     /* Don't copy over a writeback job, they are used only once */
> > > > > > > > >     state->writeback_job = NULL;
> > > > > > > > > +
> > > > > > > > > +   /* Self refresh should be canceled when a new update is available */
> > > > > > > > > +   state->self_refresh_changed = state->self_refresh_active;
> > > > > > > > > +   state->self_refresh_active = false;
> > > > > > > >
> > > > > > > > Why the duplication in self-refresh tracking? Connectors never have a
> > > > > > > > different self-refresh state, and you can always look at the right
> > > > > > > > crtc_state. Duplication just gives us the chance to screw up and get out
> > > > > > > > of sync (e.g. if the crtc for a connector changes).
> > > > > > > >
> > > > > > >
> > > > > > > On disable the crtc is cleared from connector_state, so we don't have access to
> > > > > > > it. If I add the appropriate atomic_enable/disable hooks as suggested below, we
> > > > > > > should be able to nuke these.
> > > > > >
> > > > > > Yeah we'd need the old state to look at the crtc and all that. Which is a
> > > > > > lot more trickier.
> > > > > >
> > > > > > Since it's such a special case, should we have a dedicated callback for
> > > > > > the direct self-refresh -> completely off transition? It'll be asymetric,
> > > > > > but that's the nature of this I think.
> > > > >
> > > > > Right, the asymmetry is really annoying here. If the driver is SR-aware, it makes
> > > > > sense since SR-active to disable is a real transition. However if the driver is
> > > > > not SR-aware (ie: it just gets turned off when SR becomes active), the disable
> > > > > function gets called twice without an enable. So that changes the "for every
> > > > > enable there is a disable and vice versa" assumption.
> > > > >
> > > > > This is one of the benefits of the v1 design, SR was bolted on and no existing
> > > > > rules (async/no_modeset/enable-disable pairs) were [explicitly] broken. That's
> > > > > not to say it was better, it wasn't, but it was a big consideration.
> > > > >
> > > > > So, what to do.
> > > > >
> > > > > I really like the idea that drivers shouldn't have to be SR-aware to be involved
> > > > > in the pipeline. So if we add a hook for this like you suggest, we could avoid
> > > > > calling disable twice on anything not SR-aware. We would need to add the hook on
> > > > > crtc/encoder/bridge to make sure you could mix n' match SR-aware and
> > > > > non-SR-aware devices.
> > > > >
> > > > > It probably makes sense to just add matching SR hooks at this point. Since if
> > > > > the driver is doing something special in disable, it'll need to do something
> > > > > special in enable. It also reserves enable and disable for what they've
> > > > > traditionally done. If a device is not SR-aware, it'll just fall back to the
> > > > > full enable/disable and we'll make sure to not double up on the disable in the
> > > > > helpers.
> > > > >
> > > > > So we'll keep symmetry, and avoid having an awful hook name like
> > > > > disable_from_self_refresh.. yuck!
> > > > >
> > > > > Thoughts?
> > > >
> > > > I like the asymetry actually, it has grown on a bit while working out and
> > > > pondering this :-)
> > > >
> > >
> > > I'm not quite there with you, I still think it's better to split it all out.
> > >
> > > > Benefits:
> > > > - we keep the 100% symmetry of enable/disable hooks
> > > > - self-refresh aware connector code also gets a bit simpler I think: in
> > > >   the normal enable/disable hooks it can just check for
> > > >   connector->state->crtc->state->self_refresh_active for sr state changes
> > > >   while the pipe is logically staying on
> > > > - the one asymmetric case due to this design where we disable the pipe
> > > >   harder has an awkward special hook, which gives us a great opportunity
> > > >   to explain why it's needed
> > > > - nothing changes for non-sr aware drivers
> > > > - also no need to duplicate sr state into connectors, since it's all
> > > >   fairly explit already in all three state transitions.
> > >
> > > To be fair, only one of these is exclusive to asymmetry, and it's the one that
> > > provides the opportunity to add a comment. If the sr functions are symmetric,
> > > the code becomes much more "normal" and less deserving of the explanation.
> > >
> > > The reason I would like to split out entry and exit is that it makes the driver
> > > code a bit easier to rationalize. Currently we need to check the state at the
> > > beginning of enable/disable to determine whether we want the full enable/disable
> > > or the psr exit/enter. So the sr_disable function would really just be plain
> > > old disable without the special casing at the top. In that case, we don't even
> > > need the separate function, we could just limit disable calls only on those
> > > objects which are effectively on (active || sr). That starts sounding a lot like
> > > what we already have here.
> > >
> > > Further, doing SR in enable/disable is really just legacy from v1 which tried to
> > > keep as much the same as possible. Now that we're "in it", I think it makes
> > > sense to go all in and make SR a first class citizen.
> > 
> > Hm, question is: How many hooks do you need? Just something on the
> > connector, or on the encoder, or everywhere?
> 
> bridge/encoder/crtc all do special things during SR transitions, I don't think
> connector is necessary. This is the same for any .sr_disable function, everyone
> would need to implement it.

Hm, that's a lot of new callbacks ...

> > And how do you handle the
> > various state transitions. On the disable side we have:
> > - active on -> active off, no sr (userspace disables crtc)
> > - active on, sr off -> active ooff, sr on (sr timer fires and suspends crtc)
> > - active off, sr on -> active off, sr off (userspace disable crtc
> > while crtc is in sr)
> > These are all "logical active on" -> "something" transitions where we
> > disable something (crtc, or display or both)
> > 
> > So in a way you'd need 3 hooks here for the full matrix.
> > And they all
> > kinda disable something. On the enable side we have:
> > - active off, sr off -> active on, sr off (userspace enables crtc)
> > - active off, sr on -> active on, sr off (userspace does a pageflip, stops sr)
> > Here we either enable the crtc (display already on) or both. Since we
> > only go into sr with the timer there's no 3rd case of only enabling
> > the display. So still asymetric, even with lots more hooks.
> 
> We don't need the (active off, sr on) -> (active off, sr off) (third) case
> above, it's the same as the first. Just doing a full disable is sufficient,
> so you would have symmetry in the enable/disable calls and asymmetry in the
> sr calls. This is similar to enabling a plane, or turning other HW features on
> while enabled. SR is after all just a feature of the hardware.

Hm yeah I guess we can treat it like plane disabling, which implicitly
happens in crtc->disable too. Or the implicit plane enable in crtc->enable
(although that case doesn't exist for sr, since we never go directly into
sr).

> > If you want the full matrix, there's going to be a _lot_ of hooks. I
> > think slightly more awkward driver, but less hooks is better. Hence
> > the slightly awkward middle ground of a special disable_from_sr hook.
> > But maybe there's a better option somewhere else ...
> 
> There's really no reason to even have the sr_disable function. The .disable
> function in the driver will already need special casing to detect psr_entry
> vs full disable, so it'd be better to just call disable twice. The .sr_disable
> function would always just do a full disable (ie: the .disable implementation
> without the sr checks at the top).
> 
> So the debate should be: add sr_enable/disable pair of hooks, or overload
> disable with asymmetry (current implementation).

I guess that means we're back to no new hooks, and the driver just dtrt
in the existing hooks with the state transition bits we have? I thought
the issue with that is that we can't get at all the right bits, hence the
sr_disable special case hook.

Or is your plan to roll out a full new set of hooks, equipped with
old/new_state for everything? I think we'd only need old/new_state for the
object at hand, since with the old_state you can get at drm_atomic_state,
which allows you to get anything else really.

Or should we just add drm_atomic_state *state to all these hooks? That'd
probably the most flexible long-term thing. Could even be done with cocci,
so we don't need new atomic_disable2 calls and silly things like that.
-Daniel
> 
> Sean
> 
> > -Daniel
> > 
> > >
> > > Sean
> > >
> > > >
> > > > - SR on can only happen if the logical crtc_state->active is on and stays on
> > > > - SR can get disabled in 2 subcases
> > > >   - logical active state stays on -> handled with existing hooks
> > > >   - logical active state also goes off -> existing hooks all skip (because
> > > >     active=false -> active=false is a no-op), the special ->sr_disable
> > > >     takes care
> > > >
> > > > It feels like this is clean, integrates well with atomic helpers overall
> > > > and it even makes sense. At least to my slightly oxygen deprived mind
> > > > right now ...
> > > >
> > > > > > > > >  }
> > > > > > > > >  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
> > > > > > > > >
> > > > >
> > > > > /snip
> > > > >
> > > > > > > > > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> > > > > > > > > index c8061992d6cb..0ae7e812ec62 100644
> > > > > > > > > --- a/include/drm/drm_connector.h
> > > > > > > > > +++ b/include/drm/drm_connector.h
> > > > > > > > > @@ -501,6 +501,37 @@ struct drm_connector_state {
> > > > > > > > >     /** @tv: TV connector state */
> > > > > > > > >     struct drm_tv_connector_state tv;
> > > > > > > > >
> > > > > > > > > +   /**
> > > > > > > > > +    * @self_refresh_changed:
> > > > > > > > > +    *
> > > > > > > > > +    * Set true when self refresh status has changed. This is useful for
> > > > > > > > > +    * use in encoder/bridge enable where the old state is unavailable to
> > > > > > > > > +    * the driver and it needs to know whether the enable transition is a
> > > > > > > > > +    * full transition, or if it just needs to exit self refresh mode.
> > > > > > > >
> > > > > > > > Uh, we just need proper atomic callbacks with all the states available.
> > > > > > > > Once you have one, you can get at the others.
> > > > > > > >
> > > > > > >
> > > > > > > Well, sure, we could do that too :)
> > > > > >
> > > > > > tbh I'm not sure whether that's really better, the duplication just irks
> > > > > > me. With a new callback for the special self-refresh disable (I guess we
> > > > > > only need that on the connector), plus looking at
> > > > > > connector->state->crtc->state->self_refresh, I think we'd be covered
> > > > > > as-is? Or is there a corner case I'm still missing?
> > > > > >
> > > > >
> > > > > I think we can remove self_refresh_changed/self_refresh_active if we implement
> > > > > dedicated hooks for self_refresh_enter/exit. We'll want to keep
> > > > > self_refresh_aware around since the presence of the callback implementations
> > > > > does not imply the panel connected supports SR.
> > > >
> > > > Yup, self_refresh_aware is needed.
> > > >
> > > > > As mentioned above, we'll need these hooks on everything in the pipeline to be
> > > > > fully covered.
> > > >
> > > > Let's just do the ->sr_disable hook for now. I don't think we need all the
> > > > others really.
> > > >
> > > > Cheers, Daniel
> > > > --
> > > > Daniel Vetter
> > > > Software Engineer, Intel Corporation
> > > > http://blog.ffwll.ch
> > >
> > > --
> > > Sean Paul, Software Engineer, Google / Chromium OS
> > 
> > 
> > 
> > -- 
> > Daniel Vetter
> > Software Engineer, Intel Corporation
> > +41 (0) 79 365 57 48 - http://blog.ffwll.ch
> 
> -- 
> Sean Paul, Software Engineer, Google / Chromium OS
Neil Armstrong April 2, 2019, 8:55 a.m. UTC | #11
Hi Sean,

On 26/03/2019 21:44, Sean Paul wrote:
> From: Sean Paul <seanpaul@chromium.org>
> 
> 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' and/or
> 'self_refresh_changed' state in crtc_state and connector_state. It
> should initiate self refresh mode on the panel and enter an off or
> low-power state.

It may be a stupid question, but can't this make use of PM runtime ?

Neil

> 
> 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)
> 
> Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
> Signed-off-by: Sean Paul <seanpaul@chromium.org>
> ---
>  Documentation/gpu/drm-kms-helpers.rst     |   9 +
>  drivers/gpu/drm/Makefile                  |   3 +-
>  drivers/gpu/drm/drm_atomic.c              |   4 +
>  drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
>  drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
>  drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
>  drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
>  include/drm/drm_atomic.h                  |  15 ++
>  include/drm/drm_connector.h               |  31 ++++
>  include/drm/drm_crtc.h                    |  19 ++
>  include/drm/drm_self_refresh_helper.h     |  23 +++
>  11 files changed, 360 insertions(+), 5 deletions(-)
>  create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
>  create mode 100644 include/drm/drm_self_refresh_helper.h
> 
> diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
> index 58b375e47615..b0b71f73829f 100644
> --- a/Documentation/gpu/drm-kms-helpers.rst
> +++ b/Documentation/gpu/drm-kms-helpers.rst
> @@ -107,6 +107,15 @@ fbdev Helper Functions Reference
>  .. kernel-doc:: drivers/gpu/drm/drm_fb_helper.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:
> +
>  Framebuffer CMA Helper Functions Reference
>  ==========================================
>  
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index e630eccb951c..5b3a1e26ec94 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -38,7 +38,8 @@ drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_dsc.o drm_probe_helper
>  		drm_kms_helper_common.o drm_dp_dual_mode_helper.o \
>  		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_atomic_state_helper.o drm_damage_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
> diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> index 5eb40130fafb..a06fe55b5ebf 100644
> --- a/drivers/gpu/drm/drm_atomic.c
> +++ b/drivers/gpu/drm/drm_atomic.c
> @@ -379,6 +379,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);
> @@ -881,6 +882,9 @@ 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_active=%d\n", state->self_refresh_active);
> +	drm_printf(p, "\tself_refresh_aware=%d\n", state->self_refresh_aware);
> +	drm_printf(p, "\tself_refresh_changed=%d\n", state->self_refresh_changed);
>  
>  	if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
>  		if (state->writeback_job && state->writeback_job->fb)
> diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> index 2453678d1186..c659db105133 100644
> --- a/drivers/gpu/drm/drm_atomic_helper.c
> +++ b/drivers/gpu/drm/drm_atomic_helper.c
> @@ -30,6 +30,7 @@
>  #include <drm/drm_atomic_uapi.h>
>  #include <drm/drm_plane_helper.h>
>  #include <drm/drm_atomic_helper.h>
> +#include <drm/drm_self_refresh_helper.h>
>  #include <drm/drm_writeback.h>
>  #include <drm/drm_damage_helper.h>
>  #include <linux/dma-fence.h>
> @@ -950,10 +951,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)
>  {
> @@ -974,7 +998,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;
>  
> @@ -1018,7 +1049,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;
> @@ -2948,6 +2979,7 @@ int drm_atomic_helper_set_config(struct drm_mode_set *set,
>  		return -ENOMEM;
>  
>  	state->acquire_ctx = ctx;
> +
>  	ret = __drm_atomic_helper_set_config(set, state);
>  	if (ret != 0)
>  		goto fail;
> diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
> index 4985384e51f6..ec90c527deed 100644
> --- a/drivers/gpu/drm/drm_atomic_state_helper.c
> +++ b/drivers/gpu/drm/drm_atomic_state_helper.c
> @@ -105,6 +105,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);
>  
> @@ -370,6 +374,10 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
>  
>  	/* Don't copy over a writeback job, they are used only once */
>  	state->writeback_job = NULL;
> +
> +	/* Self refresh should be canceled when a new update is available */
> +	state->self_refresh_changed = state->self_refresh_active;
> +	state->self_refresh_active = false;
>  }
>  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
>  
> diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
> index 4eb81f10bc54..d2085332172b 100644
> --- a/drivers/gpu/drm/drm_atomic_uapi.c
> +++ b/drivers/gpu/drm/drm_atomic_uapi.c
> @@ -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)
> @@ -785,7 +785,8 @@ 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;
> +		*val = state->self_refresh_active ? DRM_MODE_DPMS_ON :
> +			connector->dpms;
>  	} else if (property == config->tv_select_subconnector_property) {
>  		*val = state->tv.subconnector;
>  	} else if (property == config->tv_left_margin_property) {
> diff --git a/drivers/gpu/drm/drm_self_refresh_helper.c b/drivers/gpu/drm/drm_self_refresh_helper.c
> new file mode 100644
> index 000000000000..a3afa031480e
> --- /dev/null
> +++ b/drivers/gpu/drm/drm_self_refresh_helper.c
> @@ -0,0 +1,212 @@
> +/* SPDX-License-Identifier: MIT */
> +/*
> + * Copyright (C) 2019 Google, Inc.
> + *
> + * Authors:
> + * Sean Paul <seanpaul@chromium.org>
> + */
> +#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>
> +#include <linux/bitops.h>
> +#include <linux/slab.h>
> +#include <linux/workqueue.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 registering and unregistering the SR helpers on load/unload.
> + *
> + * Once a crtc has enabled SR, 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 a flag set in
> + * state that tells you to disable/enable SR on the panel instead of power-
> + * cycling it.
> + *
> + * Drivers may choose to fully disable their crtc/encoder/bridge hardware, or
> + * they can use the "self_refresh_active" and "self_refresh_changed" flags in
> + * object state 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 SR mode.
> + */
> +
> +struct drm_self_refresh_state {
> +	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_state *sr_state = container_of(
> +				to_delayed_work(work),
> +				struct drm_self_refresh_state, entry_work);
> +	struct drm_crtc *crtc = sr_state->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;
> +
> +	crtc_state->active = false;
> +	crtc_state->self_refresh_active = true;
> +
> +	for_each_new_connector_in_state(state, conn, conn_state, i) {
> +		if (!conn_state->self_refresh_aware)
> +			goto out;
> +
> +		conn_state->self_refresh_changed = true;
> +		conn_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) {
> +		for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> +			if (crtc_state->self_refresh_active) {
> +				state->async_update = false;
> +				break;
> +			}
> +		}
> +	}
> +	if (!state->allow_modeset) {
> +		for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> +			if (crtc_state->self_refresh_active) {
> +				state->allow_modeset = true;
> +				break;
> +			}
> +		}
> +	}
> +
> +	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
> +		struct drm_self_refresh_state *sr_state;
> +
> +		/* Don't trigger the entry timer when we're already in SR */
> +		if (crtc_state->self_refresh_active)
> +			continue;
> +
> +		sr_state = crtc->self_refresh_state;
> +		mod_delayed_work(system_wq, &sr_state->entry_work,
> +			 msecs_to_jiffies(sr_state->entry_delay_ms));
> +	}
> +}
> +EXPORT_SYMBOL(drm_self_refresh_helper_alter_state);
> +
> +/**
> + * drm_self_refresh_helper_register - Registers 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
> + */
> +int drm_self_refresh_helper_register(struct drm_crtc *crtc,
> +				     unsigned int entry_delay_ms)
> +{
> +	struct drm_self_refresh_state *sr_state = crtc->self_refresh_state;
> +
> +	/* Helper is already registered */
> +	if (WARN_ON(sr_state))
> +		return -EINVAL;
> +
> +	sr_state = kzalloc(sizeof(*sr_state), GFP_KERNEL);
> +	if (!sr_state)
> +		return -ENOMEM;
> +
> +	INIT_DELAYED_WORK(&sr_state->entry_work,
> +			  drm_self_refresh_helper_entry_work);
> +	sr_state->entry_delay_ms = entry_delay_ms;
> +	sr_state->crtc = crtc;
> +
> +	crtc->self_refresh_state = sr_state;
> +	return 0;
> +}
> +EXPORT_SYMBOL(drm_self_refresh_helper_register);
> +
> +/**
> + * drm_self_refresh_helper_unregister - Unregisters self refresh helpers
> + * @crtc: the crtc to unregister
> + */
> +void drm_self_refresh_helper_unregister(struct drm_crtc *crtc)
> +{
> +	struct drm_self_refresh_state *sr_state = crtc->self_refresh_state;
> +
> +	/* Helper is already unregistered */
> +	if (sr_state)
> +		return;
> +
> +	crtc->self_refresh_state = NULL;
> +
> +	cancel_delayed_work_sync(&sr_state->entry_work);
> +	kfree(sr_state);
> +}
> +EXPORT_SYMBOL(drm_self_refresh_helper_unregister);
> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> index 824a5ed4e216..1e9cab1da97a 100644
> --- a/include/drm/drm_atomic.h
> +++ b/include/drm/drm_atomic.h
> @@ -944,4 +944,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_ */
> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> index c8061992d6cb..0ae7e812ec62 100644
> --- a/include/drm/drm_connector.h
> +++ b/include/drm/drm_connector.h
> @@ -501,6 +501,37 @@ struct drm_connector_state {
>  	/** @tv: TV connector state */
>  	struct drm_tv_connector_state tv;
>  
> +	/**
> +	 * @self_refresh_changed:
> +	 *
> +	 * Set true when self refresh status has changed. This is useful for
> +	 * use in encoder/bridge enable where the old state is unavailable to
> +	 * the driver and it needs to know whether the enable transition is a
> +	 * full transition, or if it just needs to exit self refresh mode.
> +	 */
> +	bool self_refresh_changed;
> +
> +	/**
> +	 * @self_refresh_active:
> +	 *
> +	 * Used by the self refresh (SR) helpers to denote when the display
> +	 * should be self refreshing. If your connector is SR-capable, check
> +	 * this flag in .disable(). If it is true, instead of shutting off the
> +	 * panel, put it into self refreshing mode.
> +	 */
> +	bool self_refresh_active;
> +
> +	/**
> +	 * @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.
> +	 */
> +	bool self_refresh_aware;
> +
>  	/**
>  	 * @picture_aspect_ratio: Connector property to control the
>  	 * HDMI infoframe aspect ratio setting.
> diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
> index f7c3022dbdf4..208d68129b4c 100644
> --- a/include/drm/drm_crtc.h
> +++ b/include/drm/drm_crtc.h
> @@ -53,6 +53,7 @@ struct drm_mode_set;
>  struct drm_file;
>  struct drm_clip_rect;
>  struct drm_printer;
> +struct drm_self_refresh_state;
>  struct device_node;
>  struct dma_fence;
>  struct edid;
> @@ -299,6 +300,17 @@ struct drm_crtc_state {
>  	 */
>  	bool vrr_enabled;
>  
> +	/**
> +	 * @self_refresh_active:
> +	 *
> +	 * Used by the self refresh helpers to denote when a self refresh
> +	 * transition is occuring. This will be set on enable/disable callbacks
> +	 * when self refresh is being enabled or disabled. In some cases, it may
> +	 * not be desirable to fully shut off the crtc during self refresh.
> +	 * CRTC's can inspect this flag and determine the best course of action.
> +	 */
> +	bool self_refresh_active;
> +
>  	/**
>  	 * @event:
>  	 *
> @@ -1087,6 +1099,13 @@ struct drm_crtc {
>  	 * The name of the CRTC's fence timeline.
>  	 */
>  	char timeline_name[32];
> +
> +	/**
> +	 * @self_refresh_state: Holds the state for the self refresh helpers
> +	 *
> +	 * Initialized via drm_self_refresh_helper_register().
> +	 */
> +	struct drm_self_refresh_state *self_refresh_state;
>  };
>  
>  /**
> diff --git a/include/drm/drm_self_refresh_helper.h b/include/drm/drm_self_refresh_helper.h
> new file mode 100644
> index 000000000000..015dacb8a807
> --- /dev/null
> +++ b/include/drm/drm_self_refresh_helper.h
> @@ -0,0 +1,23 @@
> +/* SPDX-License-Identifier: MIT */
> +/*
> + * Copyright (C) 2019 Google, Inc.
> + *
> + * Authors:
> + * Sean Paul <seanpaul@chromium.org>
> + */
> +#ifndef DRM_SELF_REFRESH_HELPER_H_
> +#define DRM_SELF_REFRESH_HELPER_H_
> +
> +struct drm_atomic_state;
> +struct drm_connector;
> +struct drm_device;
> +struct drm_self_refresh_state;
> +struct drm_modeset_acquire_ctx;
> +
> +void drm_self_refresh_helper_alter_state(struct drm_atomic_state *state);
> +
> +int drm_self_refresh_helper_register(struct drm_crtc *crtc,
> +				     unsigned int entry_delay_ms);
> +
> +void drm_self_refresh_helper_unregister(struct drm_crtc *crtc);
> +#endif
>
Daniel Vetter April 2, 2019, 9:08 a.m. UTC | #12
On Tue, Apr 02, 2019 at 10:55:24AM +0200, Neil Armstrong wrote:
> Hi Sean,
> 
> On 26/03/2019 21:44, Sean Paul wrote:
> > From: Sean Paul <seanpaul@chromium.org>
> > 
> > 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' and/or
> > 'self_refresh_changed' state in crtc_state and connector_state. It
> > should initiate self refresh mode on the panel and enter an off or
> > low-power state.
> 
> It may be a stupid question, but can't this make use of PM runtime ?

tbh no idea what you have in mind ... Can you pls explain a bit more?
-Daniel

> 
> Neil
> 
> > 
> > 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)
> > 
> > Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
> > Signed-off-by: Sean Paul <seanpaul@chromium.org>
> > ---
> >  Documentation/gpu/drm-kms-helpers.rst     |   9 +
> >  drivers/gpu/drm/Makefile                  |   3 +-
> >  drivers/gpu/drm/drm_atomic.c              |   4 +
> >  drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
> >  drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
> >  drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
> >  drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
> >  include/drm/drm_atomic.h                  |  15 ++
> >  include/drm/drm_connector.h               |  31 ++++
> >  include/drm/drm_crtc.h                    |  19 ++
> >  include/drm/drm_self_refresh_helper.h     |  23 +++
> >  11 files changed, 360 insertions(+), 5 deletions(-)
> >  create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
> >  create mode 100644 include/drm/drm_self_refresh_helper.h
> > 
> > diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
> > index 58b375e47615..b0b71f73829f 100644
> > --- a/Documentation/gpu/drm-kms-helpers.rst
> > +++ b/Documentation/gpu/drm-kms-helpers.rst
> > @@ -107,6 +107,15 @@ fbdev Helper Functions Reference
> >  .. kernel-doc:: drivers/gpu/drm/drm_fb_helper.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:
> > +
> >  Framebuffer CMA Helper Functions Reference
> >  ==========================================
> >  
> > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > index e630eccb951c..5b3a1e26ec94 100644
> > --- a/drivers/gpu/drm/Makefile
> > +++ b/drivers/gpu/drm/Makefile
> > @@ -38,7 +38,8 @@ drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_dsc.o drm_probe_helper
> >  		drm_kms_helper_common.o drm_dp_dual_mode_helper.o \
> >  		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_atomic_state_helper.o drm_damage_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
> > diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> > index 5eb40130fafb..a06fe55b5ebf 100644
> > --- a/drivers/gpu/drm/drm_atomic.c
> > +++ b/drivers/gpu/drm/drm_atomic.c
> > @@ -379,6 +379,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);
> > @@ -881,6 +882,9 @@ 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_active=%d\n", state->self_refresh_active);
> > +	drm_printf(p, "\tself_refresh_aware=%d\n", state->self_refresh_aware);
> > +	drm_printf(p, "\tself_refresh_changed=%d\n", state->self_refresh_changed);
> >  
> >  	if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
> >  		if (state->writeback_job && state->writeback_job->fb)
> > diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> > index 2453678d1186..c659db105133 100644
> > --- a/drivers/gpu/drm/drm_atomic_helper.c
> > +++ b/drivers/gpu/drm/drm_atomic_helper.c
> > @@ -30,6 +30,7 @@
> >  #include <drm/drm_atomic_uapi.h>
> >  #include <drm/drm_plane_helper.h>
> >  #include <drm/drm_atomic_helper.h>
> > +#include <drm/drm_self_refresh_helper.h>
> >  #include <drm/drm_writeback.h>
> >  #include <drm/drm_damage_helper.h>
> >  #include <linux/dma-fence.h>
> > @@ -950,10 +951,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)
> >  {
> > @@ -974,7 +998,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;
> >  
> > @@ -1018,7 +1049,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;
> > @@ -2948,6 +2979,7 @@ int drm_atomic_helper_set_config(struct drm_mode_set *set,
> >  		return -ENOMEM;
> >  
> >  	state->acquire_ctx = ctx;
> > +
> >  	ret = __drm_atomic_helper_set_config(set, state);
> >  	if (ret != 0)
> >  		goto fail;
> > diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
> > index 4985384e51f6..ec90c527deed 100644
> > --- a/drivers/gpu/drm/drm_atomic_state_helper.c
> > +++ b/drivers/gpu/drm/drm_atomic_state_helper.c
> > @@ -105,6 +105,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);
> >  
> > @@ -370,6 +374,10 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> >  
> >  	/* Don't copy over a writeback job, they are used only once */
> >  	state->writeback_job = NULL;
> > +
> > +	/* Self refresh should be canceled when a new update is available */
> > +	state->self_refresh_changed = state->self_refresh_active;
> > +	state->self_refresh_active = false;
> >  }
> >  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
> >  
> > diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
> > index 4eb81f10bc54..d2085332172b 100644
> > --- a/drivers/gpu/drm/drm_atomic_uapi.c
> > +++ b/drivers/gpu/drm/drm_atomic_uapi.c
> > @@ -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)
> > @@ -785,7 +785,8 @@ 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;
> > +		*val = state->self_refresh_active ? DRM_MODE_DPMS_ON :
> > +			connector->dpms;
> >  	} else if (property == config->tv_select_subconnector_property) {
> >  		*val = state->tv.subconnector;
> >  	} else if (property == config->tv_left_margin_property) {
> > diff --git a/drivers/gpu/drm/drm_self_refresh_helper.c b/drivers/gpu/drm/drm_self_refresh_helper.c
> > new file mode 100644
> > index 000000000000..a3afa031480e
> > --- /dev/null
> > +++ b/drivers/gpu/drm/drm_self_refresh_helper.c
> > @@ -0,0 +1,212 @@
> > +/* SPDX-License-Identifier: MIT */
> > +/*
> > + * Copyright (C) 2019 Google, Inc.
> > + *
> > + * Authors:
> > + * Sean Paul <seanpaul@chromium.org>
> > + */
> > +#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>
> > +#include <linux/bitops.h>
> > +#include <linux/slab.h>
> > +#include <linux/workqueue.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 registering and unregistering the SR helpers on load/unload.
> > + *
> > + * Once a crtc has enabled SR, 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 a flag set in
> > + * state that tells you to disable/enable SR on the panel instead of power-
> > + * cycling it.
> > + *
> > + * Drivers may choose to fully disable their crtc/encoder/bridge hardware, or
> > + * they can use the "self_refresh_active" and "self_refresh_changed" flags in
> > + * object state 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 SR mode.
> > + */
> > +
> > +struct drm_self_refresh_state {
> > +	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_state *sr_state = container_of(
> > +				to_delayed_work(work),
> > +				struct drm_self_refresh_state, entry_work);
> > +	struct drm_crtc *crtc = sr_state->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;
> > +
> > +	crtc_state->active = false;
> > +	crtc_state->self_refresh_active = true;
> > +
> > +	for_each_new_connector_in_state(state, conn, conn_state, i) {
> > +		if (!conn_state->self_refresh_aware)
> > +			goto out;
> > +
> > +		conn_state->self_refresh_changed = true;
> > +		conn_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) {
> > +		for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> > +			if (crtc_state->self_refresh_active) {
> > +				state->async_update = false;
> > +				break;
> > +			}
> > +		}
> > +	}
> > +	if (!state->allow_modeset) {
> > +		for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> > +			if (crtc_state->self_refresh_active) {
> > +				state->allow_modeset = true;
> > +				break;
> > +			}
> > +		}
> > +	}
> > +
> > +	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
> > +		struct drm_self_refresh_state *sr_state;
> > +
> > +		/* Don't trigger the entry timer when we're already in SR */
> > +		if (crtc_state->self_refresh_active)
> > +			continue;
> > +
> > +		sr_state = crtc->self_refresh_state;
> > +		mod_delayed_work(system_wq, &sr_state->entry_work,
> > +			 msecs_to_jiffies(sr_state->entry_delay_ms));
> > +	}
> > +}
> > +EXPORT_SYMBOL(drm_self_refresh_helper_alter_state);
> > +
> > +/**
> > + * drm_self_refresh_helper_register - Registers 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
> > + */
> > +int drm_self_refresh_helper_register(struct drm_crtc *crtc,
> > +				     unsigned int entry_delay_ms)
> > +{
> > +	struct drm_self_refresh_state *sr_state = crtc->self_refresh_state;
> > +
> > +	/* Helper is already registered */
> > +	if (WARN_ON(sr_state))
> > +		return -EINVAL;
> > +
> > +	sr_state = kzalloc(sizeof(*sr_state), GFP_KERNEL);
> > +	if (!sr_state)
> > +		return -ENOMEM;
> > +
> > +	INIT_DELAYED_WORK(&sr_state->entry_work,
> > +			  drm_self_refresh_helper_entry_work);
> > +	sr_state->entry_delay_ms = entry_delay_ms;
> > +	sr_state->crtc = crtc;
> > +
> > +	crtc->self_refresh_state = sr_state;
> > +	return 0;
> > +}
> > +EXPORT_SYMBOL(drm_self_refresh_helper_register);
> > +
> > +/**
> > + * drm_self_refresh_helper_unregister - Unregisters self refresh helpers
> > + * @crtc: the crtc to unregister
> > + */
> > +void drm_self_refresh_helper_unregister(struct drm_crtc *crtc)
> > +{
> > +	struct drm_self_refresh_state *sr_state = crtc->self_refresh_state;
> > +
> > +	/* Helper is already unregistered */
> > +	if (sr_state)
> > +		return;
> > +
> > +	crtc->self_refresh_state = NULL;
> > +
> > +	cancel_delayed_work_sync(&sr_state->entry_work);
> > +	kfree(sr_state);
> > +}
> > +EXPORT_SYMBOL(drm_self_refresh_helper_unregister);
> > diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> > index 824a5ed4e216..1e9cab1da97a 100644
> > --- a/include/drm/drm_atomic.h
> > +++ b/include/drm/drm_atomic.h
> > @@ -944,4 +944,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_ */
> > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> > index c8061992d6cb..0ae7e812ec62 100644
> > --- a/include/drm/drm_connector.h
> > +++ b/include/drm/drm_connector.h
> > @@ -501,6 +501,37 @@ struct drm_connector_state {
> >  	/** @tv: TV connector state */
> >  	struct drm_tv_connector_state tv;
> >  
> > +	/**
> > +	 * @self_refresh_changed:
> > +	 *
> > +	 * Set true when self refresh status has changed. This is useful for
> > +	 * use in encoder/bridge enable where the old state is unavailable to
> > +	 * the driver and it needs to know whether the enable transition is a
> > +	 * full transition, or if it just needs to exit self refresh mode.
> > +	 */
> > +	bool self_refresh_changed;
> > +
> > +	/**
> > +	 * @self_refresh_active:
> > +	 *
> > +	 * Used by the self refresh (SR) helpers to denote when the display
> > +	 * should be self refreshing. If your connector is SR-capable, check
> > +	 * this flag in .disable(). If it is true, instead of shutting off the
> > +	 * panel, put it into self refreshing mode.
> > +	 */
> > +	bool self_refresh_active;
> > +
> > +	/**
> > +	 * @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.
> > +	 */
> > +	bool self_refresh_aware;
> > +
> >  	/**
> >  	 * @picture_aspect_ratio: Connector property to control the
> >  	 * HDMI infoframe aspect ratio setting.
> > diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
> > index f7c3022dbdf4..208d68129b4c 100644
> > --- a/include/drm/drm_crtc.h
> > +++ b/include/drm/drm_crtc.h
> > @@ -53,6 +53,7 @@ struct drm_mode_set;
> >  struct drm_file;
> >  struct drm_clip_rect;
> >  struct drm_printer;
> > +struct drm_self_refresh_state;
> >  struct device_node;
> >  struct dma_fence;
> >  struct edid;
> > @@ -299,6 +300,17 @@ struct drm_crtc_state {
> >  	 */
> >  	bool vrr_enabled;
> >  
> > +	/**
> > +	 * @self_refresh_active:
> > +	 *
> > +	 * Used by the self refresh helpers to denote when a self refresh
> > +	 * transition is occuring. This will be set on enable/disable callbacks
> > +	 * when self refresh is being enabled or disabled. In some cases, it may
> > +	 * not be desirable to fully shut off the crtc during self refresh.
> > +	 * CRTC's can inspect this flag and determine the best course of action.
> > +	 */
> > +	bool self_refresh_active;
> > +
> >  	/**
> >  	 * @event:
> >  	 *
> > @@ -1087,6 +1099,13 @@ struct drm_crtc {
> >  	 * The name of the CRTC's fence timeline.
> >  	 */
> >  	char timeline_name[32];
> > +
> > +	/**
> > +	 * @self_refresh_state: Holds the state for the self refresh helpers
> > +	 *
> > +	 * Initialized via drm_self_refresh_helper_register().
> > +	 */
> > +	struct drm_self_refresh_state *self_refresh_state;
> >  };
> >  
> >  /**
> > diff --git a/include/drm/drm_self_refresh_helper.h b/include/drm/drm_self_refresh_helper.h
> > new file mode 100644
> > index 000000000000..015dacb8a807
> > --- /dev/null
> > +++ b/include/drm/drm_self_refresh_helper.h
> > @@ -0,0 +1,23 @@
> > +/* SPDX-License-Identifier: MIT */
> > +/*
> > + * Copyright (C) 2019 Google, Inc.
> > + *
> > + * Authors:
> > + * Sean Paul <seanpaul@chromium.org>
> > + */
> > +#ifndef DRM_SELF_REFRESH_HELPER_H_
> > +#define DRM_SELF_REFRESH_HELPER_H_
> > +
> > +struct drm_atomic_state;
> > +struct drm_connector;
> > +struct drm_device;
> > +struct drm_self_refresh_state;
> > +struct drm_modeset_acquire_ctx;
> > +
> > +void drm_self_refresh_helper_alter_state(struct drm_atomic_state *state);
> > +
> > +int drm_self_refresh_helper_register(struct drm_crtc *crtc,
> > +				     unsigned int entry_delay_ms);
> > +
> > +void drm_self_refresh_helper_unregister(struct drm_crtc *crtc);
> > +#endif
> > 
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
Neil Armstrong April 2, 2019, 9:45 a.m. UTC | #13
On 02/04/2019 11:08, Daniel Vetter wrote:
> On Tue, Apr 02, 2019 at 10:55:24AM +0200, Neil Armstrong wrote:
>> Hi Sean,
>>
>> On 26/03/2019 21:44, Sean Paul wrote:
>>> From: Sean Paul <seanpaul@chromium.org>
>>>
>>> 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' and/or
>>> 'self_refresh_changed' state in crtc_state and connector_state. It
>>> should initiate self refresh mode on the panel and enter an off or
>>> low-power state.
>>
>> It may be a stupid question, but can't this make use of PM runtime ?
> 
> tbh no idea what you have in mind ... Can you pls explain a bit more?

PM runtime is done to handle these inactivity periods, and act accordingly
in PM runtime callbacks.
Maybe Sean needs a more CRTC-centric PM runtime, to put a particular memory
in self-refresh, maybe a driver-wide inactivity tracking is too big.

Neil

> -Daniel
> 
>>
>> Neil
>>
>>>
>>> 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)
>>>
>>> Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
>>> Signed-off-by: Sean Paul <seanpaul@chromium.org>
>>> ---
>>>  Documentation/gpu/drm-kms-helpers.rst     |   9 +
>>>  drivers/gpu/drm/Makefile                  |   3 +-
>>>  drivers/gpu/drm/drm_atomic.c              |   4 +
>>>  drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
>>>  drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
>>>  drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
>>>  drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
>>>  include/drm/drm_atomic.h                  |  15 ++
>>>  include/drm/drm_connector.h               |  31 ++++
>>>  include/drm/drm_crtc.h                    |  19 ++
>>>  include/drm/drm_self_refresh_helper.h     |  23 +++
>>>  11 files changed, 360 insertions(+), 5 deletions(-)
>>>  create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
>>>  create mode 100644 include/drm/drm_self_refresh_helper.h
>>>
>>> diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
>>> index 58b375e47615..b0b71f73829f 100644
>>> --- a/Documentation/gpu/drm-kms-helpers.rst
>>> +++ b/Documentation/gpu/drm-kms-helpers.rst
>>> @@ -107,6 +107,15 @@ fbdev Helper Functions Reference
>>>  .. kernel-doc:: drivers/gpu/drm/drm_fb_helper.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:
>>> +
>>>  Framebuffer CMA Helper Functions Reference
>>>  ==========================================
>>>  
>>> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
>>> index e630eccb951c..5b3a1e26ec94 100644
>>> --- a/drivers/gpu/drm/Makefile
>>> +++ b/drivers/gpu/drm/Makefile
>>> @@ -38,7 +38,8 @@ drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_dsc.o drm_probe_helper
>>>  		drm_kms_helper_common.o drm_dp_dual_mode_helper.o \
>>>  		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_atomic_state_helper.o drm_damage_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
>>> diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
>>> index 5eb40130fafb..a06fe55b5ebf 100644
>>> --- a/drivers/gpu/drm/drm_atomic.c
>>> +++ b/drivers/gpu/drm/drm_atomic.c
>>> @@ -379,6 +379,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);
>>> @@ -881,6 +882,9 @@ 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_active=%d\n", state->self_refresh_active);
>>> +	drm_printf(p, "\tself_refresh_aware=%d\n", state->self_refresh_aware);
>>> +	drm_printf(p, "\tself_refresh_changed=%d\n", state->self_refresh_changed);
>>>  
>>>  	if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
>>>  		if (state->writeback_job && state->writeback_job->fb)
>>> diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
>>> index 2453678d1186..c659db105133 100644
>>> --- a/drivers/gpu/drm/drm_atomic_helper.c
>>> +++ b/drivers/gpu/drm/drm_atomic_helper.c
>>> @@ -30,6 +30,7 @@
>>>  #include <drm/drm_atomic_uapi.h>
>>>  #include <drm/drm_plane_helper.h>
>>>  #include <drm/drm_atomic_helper.h>
>>> +#include <drm/drm_self_refresh_helper.h>
>>>  #include <drm/drm_writeback.h>
>>>  #include <drm/drm_damage_helper.h>
>>>  #include <linux/dma-fence.h>
>>> @@ -950,10 +951,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)
>>>  {
>>> @@ -974,7 +998,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;
>>>  
>>> @@ -1018,7 +1049,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;
>>> @@ -2948,6 +2979,7 @@ int drm_atomic_helper_set_config(struct drm_mode_set *set,
>>>  		return -ENOMEM;
>>>  
>>>  	state->acquire_ctx = ctx;
>>> +
>>>  	ret = __drm_atomic_helper_set_config(set, state);
>>>  	if (ret != 0)
>>>  		goto fail;
>>> diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
>>> index 4985384e51f6..ec90c527deed 100644
>>> --- a/drivers/gpu/drm/drm_atomic_state_helper.c
>>> +++ b/drivers/gpu/drm/drm_atomic_state_helper.c
>>> @@ -105,6 +105,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);
>>>  
>>> @@ -370,6 +374,10 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
>>>  
>>>  	/* Don't copy over a writeback job, they are used only once */
>>>  	state->writeback_job = NULL;
>>> +
>>> +	/* Self refresh should be canceled when a new update is available */
>>> +	state->self_refresh_changed = state->self_refresh_active;
>>> +	state->self_refresh_active = false;
>>>  }
>>>  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
>>>  
>>> diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
>>> index 4eb81f10bc54..d2085332172b 100644
>>> --- a/drivers/gpu/drm/drm_atomic_uapi.c
>>> +++ b/drivers/gpu/drm/drm_atomic_uapi.c
>>> @@ -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)
>>> @@ -785,7 +785,8 @@ 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;
>>> +		*val = state->self_refresh_active ? DRM_MODE_DPMS_ON :
>>> +			connector->dpms;
>>>  	} else if (property == config->tv_select_subconnector_property) {
>>>  		*val = state->tv.subconnector;
>>>  	} else if (property == config->tv_left_margin_property) {
>>> diff --git a/drivers/gpu/drm/drm_self_refresh_helper.c b/drivers/gpu/drm/drm_self_refresh_helper.c
>>> new file mode 100644
>>> index 000000000000..a3afa031480e
>>> --- /dev/null
>>> +++ b/drivers/gpu/drm/drm_self_refresh_helper.c
>>> @@ -0,0 +1,212 @@
>>> +/* SPDX-License-Identifier: MIT */
>>> +/*
>>> + * Copyright (C) 2019 Google, Inc.
>>> + *
>>> + * Authors:
>>> + * Sean Paul <seanpaul@chromium.org>
>>> + */
>>> +#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>
>>> +#include <linux/bitops.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/workqueue.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 registering and unregistering the SR helpers on load/unload.
>>> + *
>>> + * Once a crtc has enabled SR, 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 a flag set in
>>> + * state that tells you to disable/enable SR on the panel instead of power-
>>> + * cycling it.
>>> + *
>>> + * Drivers may choose to fully disable their crtc/encoder/bridge hardware, or
>>> + * they can use the "self_refresh_active" and "self_refresh_changed" flags in
>>> + * object state 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 SR mode.
>>> + */
>>> +
>>> +struct drm_self_refresh_state {
>>> +	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_state *sr_state = container_of(
>>> +				to_delayed_work(work),
>>> +				struct drm_self_refresh_state, entry_work);
>>> +	struct drm_crtc *crtc = sr_state->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;
>>> +
>>> +	crtc_state->active = false;
>>> +	crtc_state->self_refresh_active = true;
>>> +
>>> +	for_each_new_connector_in_state(state, conn, conn_state, i) {
>>> +		if (!conn_state->self_refresh_aware)
>>> +			goto out;
>>> +
>>> +		conn_state->self_refresh_changed = true;
>>> +		conn_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) {
>>> +		for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
>>> +			if (crtc_state->self_refresh_active) {
>>> +				state->async_update = false;
>>> +				break;
>>> +			}
>>> +		}
>>> +	}
>>> +	if (!state->allow_modeset) {
>>> +		for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
>>> +			if (crtc_state->self_refresh_active) {
>>> +				state->allow_modeset = true;
>>> +				break;
>>> +			}
>>> +		}
>>> +	}
>>> +
>>> +	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
>>> +		struct drm_self_refresh_state *sr_state;
>>> +
>>> +		/* Don't trigger the entry timer when we're already in SR */
>>> +		if (crtc_state->self_refresh_active)
>>> +			continue;
>>> +
>>> +		sr_state = crtc->self_refresh_state;
>>> +		mod_delayed_work(system_wq, &sr_state->entry_work,
>>> +			 msecs_to_jiffies(sr_state->entry_delay_ms));
>>> +	}
>>> +}
>>> +EXPORT_SYMBOL(drm_self_refresh_helper_alter_state);
>>> +
>>> +/**
>>> + * drm_self_refresh_helper_register - Registers 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
>>> + */
>>> +int drm_self_refresh_helper_register(struct drm_crtc *crtc,
>>> +				     unsigned int entry_delay_ms)
>>> +{
>>> +	struct drm_self_refresh_state *sr_state = crtc->self_refresh_state;
>>> +
>>> +	/* Helper is already registered */
>>> +	if (WARN_ON(sr_state))
>>> +		return -EINVAL;
>>> +
>>> +	sr_state = kzalloc(sizeof(*sr_state), GFP_KERNEL);
>>> +	if (!sr_state)
>>> +		return -ENOMEM;
>>> +
>>> +	INIT_DELAYED_WORK(&sr_state->entry_work,
>>> +			  drm_self_refresh_helper_entry_work);
>>> +	sr_state->entry_delay_ms = entry_delay_ms;
>>> +	sr_state->crtc = crtc;
>>> +
>>> +	crtc->self_refresh_state = sr_state;
>>> +	return 0;
>>> +}
>>> +EXPORT_SYMBOL(drm_self_refresh_helper_register);
>>> +
>>> +/**
>>> + * drm_self_refresh_helper_unregister - Unregisters self refresh helpers
>>> + * @crtc: the crtc to unregister
>>> + */
>>> +void drm_self_refresh_helper_unregister(struct drm_crtc *crtc)
>>> +{
>>> +	struct drm_self_refresh_state *sr_state = crtc->self_refresh_state;
>>> +
>>> +	/* Helper is already unregistered */
>>> +	if (sr_state)
>>> +		return;
>>> +
>>> +	crtc->self_refresh_state = NULL;
>>> +
>>> +	cancel_delayed_work_sync(&sr_state->entry_work);
>>> +	kfree(sr_state);
>>> +}
>>> +EXPORT_SYMBOL(drm_self_refresh_helper_unregister);
>>> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
>>> index 824a5ed4e216..1e9cab1da97a 100644
>>> --- a/include/drm/drm_atomic.h
>>> +++ b/include/drm/drm_atomic.h
>>> @@ -944,4 +944,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_ */
>>> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
>>> index c8061992d6cb..0ae7e812ec62 100644
>>> --- a/include/drm/drm_connector.h
>>> +++ b/include/drm/drm_connector.h
>>> @@ -501,6 +501,37 @@ struct drm_connector_state {
>>>  	/** @tv: TV connector state */
>>>  	struct drm_tv_connector_state tv;
>>>  
>>> +	/**
>>> +	 * @self_refresh_changed:
>>> +	 *
>>> +	 * Set true when self refresh status has changed. This is useful for
>>> +	 * use in encoder/bridge enable where the old state is unavailable to
>>> +	 * the driver and it needs to know whether the enable transition is a
>>> +	 * full transition, or if it just needs to exit self refresh mode.
>>> +	 */
>>> +	bool self_refresh_changed;
>>> +
>>> +	/**
>>> +	 * @self_refresh_active:
>>> +	 *
>>> +	 * Used by the self refresh (SR) helpers to denote when the display
>>> +	 * should be self refreshing. If your connector is SR-capable, check
>>> +	 * this flag in .disable(). If it is true, instead of shutting off the
>>> +	 * panel, put it into self refreshing mode.
>>> +	 */
>>> +	bool self_refresh_active;
>>> +
>>> +	/**
>>> +	 * @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.
>>> +	 */
>>> +	bool self_refresh_aware;
>>> +
>>>  	/**
>>>  	 * @picture_aspect_ratio: Connector property to control the
>>>  	 * HDMI infoframe aspect ratio setting.
>>> diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
>>> index f7c3022dbdf4..208d68129b4c 100644
>>> --- a/include/drm/drm_crtc.h
>>> +++ b/include/drm/drm_crtc.h
>>> @@ -53,6 +53,7 @@ struct drm_mode_set;
>>>  struct drm_file;
>>>  struct drm_clip_rect;
>>>  struct drm_printer;
>>> +struct drm_self_refresh_state;
>>>  struct device_node;
>>>  struct dma_fence;
>>>  struct edid;
>>> @@ -299,6 +300,17 @@ struct drm_crtc_state {
>>>  	 */
>>>  	bool vrr_enabled;
>>>  
>>> +	/**
>>> +	 * @self_refresh_active:
>>> +	 *
>>> +	 * Used by the self refresh helpers to denote when a self refresh
>>> +	 * transition is occuring. This will be set on enable/disable callbacks
>>> +	 * when self refresh is being enabled or disabled. In some cases, it may
>>> +	 * not be desirable to fully shut off the crtc during self refresh.
>>> +	 * CRTC's can inspect this flag and determine the best course of action.
>>> +	 */
>>> +	bool self_refresh_active;
>>> +
>>>  	/**
>>>  	 * @event:
>>>  	 *
>>> @@ -1087,6 +1099,13 @@ struct drm_crtc {
>>>  	 * The name of the CRTC's fence timeline.
>>>  	 */
>>>  	char timeline_name[32];
>>> +
>>> +	/**
>>> +	 * @self_refresh_state: Holds the state for the self refresh helpers
>>> +	 *
>>> +	 * Initialized via drm_self_refresh_helper_register().
>>> +	 */
>>> +	struct drm_self_refresh_state *self_refresh_state;
>>>  };
>>>  
>>>  /**
>>> diff --git a/include/drm/drm_self_refresh_helper.h b/include/drm/drm_self_refresh_helper.h
>>> new file mode 100644
>>> index 000000000000..015dacb8a807
>>> --- /dev/null
>>> +++ b/include/drm/drm_self_refresh_helper.h
>>> @@ -0,0 +1,23 @@
>>> +/* SPDX-License-Identifier: MIT */
>>> +/*
>>> + * Copyright (C) 2019 Google, Inc.
>>> + *
>>> + * Authors:
>>> + * Sean Paul <seanpaul@chromium.org>
>>> + */
>>> +#ifndef DRM_SELF_REFRESH_HELPER_H_
>>> +#define DRM_SELF_REFRESH_HELPER_H_
>>> +
>>> +struct drm_atomic_state;
>>> +struct drm_connector;
>>> +struct drm_device;
>>> +struct drm_self_refresh_state;
>>> +struct drm_modeset_acquire_ctx;
>>> +
>>> +void drm_self_refresh_helper_alter_state(struct drm_atomic_state *state);
>>> +
>>> +int drm_self_refresh_helper_register(struct drm_crtc *crtc,
>>> +				     unsigned int entry_delay_ms);
>>> +
>>> +void drm_self_refresh_helper_unregister(struct drm_crtc *crtc);
>>> +#endif
>>>
>>
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel@lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/dri-devel
>
Daniel Vetter April 2, 2019, 9:50 a.m. UTC | #14
On Tue, Apr 2, 2019 at 11:45 AM Neil Armstrong <narmstrong@baylibre.com> wrote:
>
> On 02/04/2019 11:08, Daniel Vetter wrote:
> > On Tue, Apr 02, 2019 at 10:55:24AM +0200, Neil Armstrong wrote:
> >> Hi Sean,
> >>
> >> On 26/03/2019 21:44, Sean Paul wrote:
> >>> From: Sean Paul <seanpaul@chromium.org>
> >>>
> >>> 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' and/or
> >>> 'self_refresh_changed' state in crtc_state and connector_state. It
> >>> should initiate self refresh mode on the panel and enter an off or
> >>> low-power state.
> >>
> >> It may be a stupid question, but can't this make use of PM runtime ?
> >
> > tbh no idea what you have in mind ... Can you pls explain a bit more?
>
> PM runtime is done to handle these inactivity periods, and act accordingly
> in PM runtime callbacks.
> Maybe Sean needs a more CRTC-centric PM runtime, to put a particular memory
> in self-refresh, maybe a driver-wide inactivity tracking is too big.

I guess the actual implementation uses runtime pm (or at least can).
The trouble with self-refresh is that the display actually is in use,
and we need to keep pretending to userspace that it is in use. That
semantic lie needs a bit of work in frameworks to make it all pan out.
Part of my motiviation with repurposing the existing enable/disable
callbacks is that those will already do runtime pm (or something
equivalent), so less code. But there's a few corner cases.
-Daniel

>
> Neil
>
> > -Daniel
> >
> >>
> >> Neil
> >>
> >>>
> >>> 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)
> >>>
> >>> Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
> >>> Signed-off-by: Sean Paul <seanpaul@chromium.org>
> >>> ---
> >>>  Documentation/gpu/drm-kms-helpers.rst     |   9 +
> >>>  drivers/gpu/drm/Makefile                  |   3 +-
> >>>  drivers/gpu/drm/drm_atomic.c              |   4 +
> >>>  drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
> >>>  drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
> >>>  drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
> >>>  drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
> >>>  include/drm/drm_atomic.h                  |  15 ++
> >>>  include/drm/drm_connector.h               |  31 ++++
> >>>  include/drm/drm_crtc.h                    |  19 ++
> >>>  include/drm/drm_self_refresh_helper.h     |  23 +++
> >>>  11 files changed, 360 insertions(+), 5 deletions(-)
> >>>  create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
> >>>  create mode 100644 include/drm/drm_self_refresh_helper.h
> >>>
> >>> diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
> >>> index 58b375e47615..b0b71f73829f 100644
> >>> --- a/Documentation/gpu/drm-kms-helpers.rst
> >>> +++ b/Documentation/gpu/drm-kms-helpers.rst
> >>> @@ -107,6 +107,15 @@ fbdev Helper Functions Reference
> >>>  .. kernel-doc:: drivers/gpu/drm/drm_fb_helper.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:
> >>> +
> >>>  Framebuffer CMA Helper Functions Reference
> >>>  ==========================================
> >>>
> >>> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> >>> index e630eccb951c..5b3a1e26ec94 100644
> >>> --- a/drivers/gpu/drm/Makefile
> >>> +++ b/drivers/gpu/drm/Makefile
> >>> @@ -38,7 +38,8 @@ drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_dsc.o drm_probe_helper
> >>>             drm_kms_helper_common.o drm_dp_dual_mode_helper.o \
> >>>             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_atomic_state_helper.o drm_damage_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
> >>> diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> >>> index 5eb40130fafb..a06fe55b5ebf 100644
> >>> --- a/drivers/gpu/drm/drm_atomic.c
> >>> +++ b/drivers/gpu/drm/drm_atomic.c
> >>> @@ -379,6 +379,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);
> >>> @@ -881,6 +882,9 @@ 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_active=%d\n", state->self_refresh_active);
> >>> +   drm_printf(p, "\tself_refresh_aware=%d\n", state->self_refresh_aware);
> >>> +   drm_printf(p, "\tself_refresh_changed=%d\n", state->self_refresh_changed);
> >>>
> >>>     if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
> >>>             if (state->writeback_job && state->writeback_job->fb)
> >>> diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> >>> index 2453678d1186..c659db105133 100644
> >>> --- a/drivers/gpu/drm/drm_atomic_helper.c
> >>> +++ b/drivers/gpu/drm/drm_atomic_helper.c
> >>> @@ -30,6 +30,7 @@
> >>>  #include <drm/drm_atomic_uapi.h>
> >>>  #include <drm/drm_plane_helper.h>
> >>>  #include <drm/drm_atomic_helper.h>
> >>> +#include <drm/drm_self_refresh_helper.h>
> >>>  #include <drm/drm_writeback.h>
> >>>  #include <drm/drm_damage_helper.h>
> >>>  #include <linux/dma-fence.h>
> >>> @@ -950,10 +951,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)
> >>>  {
> >>> @@ -974,7 +998,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;
> >>>
> >>> @@ -1018,7 +1049,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;
> >>> @@ -2948,6 +2979,7 @@ int drm_atomic_helper_set_config(struct drm_mode_set *set,
> >>>             return -ENOMEM;
> >>>
> >>>     state->acquire_ctx = ctx;
> >>> +
> >>>     ret = __drm_atomic_helper_set_config(set, state);
> >>>     if (ret != 0)
> >>>             goto fail;
> >>> diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
> >>> index 4985384e51f6..ec90c527deed 100644
> >>> --- a/drivers/gpu/drm/drm_atomic_state_helper.c
> >>> +++ b/drivers/gpu/drm/drm_atomic_state_helper.c
> >>> @@ -105,6 +105,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);
> >>>
> >>> @@ -370,6 +374,10 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> >>>
> >>>     /* Don't copy over a writeback job, they are used only once */
> >>>     state->writeback_job = NULL;
> >>> +
> >>> +   /* Self refresh should be canceled when a new update is available */
> >>> +   state->self_refresh_changed = state->self_refresh_active;
> >>> +   state->self_refresh_active = false;
> >>>  }
> >>>  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
> >>>
> >>> diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
> >>> index 4eb81f10bc54..d2085332172b 100644
> >>> --- a/drivers/gpu/drm/drm_atomic_uapi.c
> >>> +++ b/drivers/gpu/drm/drm_atomic_uapi.c
> >>> @@ -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)
> >>> @@ -785,7 +785,8 @@ 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;
> >>> +           *val = state->self_refresh_active ? DRM_MODE_DPMS_ON :
> >>> +                   connector->dpms;
> >>>     } else if (property == config->tv_select_subconnector_property) {
> >>>             *val = state->tv.subconnector;
> >>>     } else if (property == config->tv_left_margin_property) {
> >>> diff --git a/drivers/gpu/drm/drm_self_refresh_helper.c b/drivers/gpu/drm/drm_self_refresh_helper.c
> >>> new file mode 100644
> >>> index 000000000000..a3afa031480e
> >>> --- /dev/null
> >>> +++ b/drivers/gpu/drm/drm_self_refresh_helper.c
> >>> @@ -0,0 +1,212 @@
> >>> +/* SPDX-License-Identifier: MIT */
> >>> +/*
> >>> + * Copyright (C) 2019 Google, Inc.
> >>> + *
> >>> + * Authors:
> >>> + * Sean Paul <seanpaul@chromium.org>
> >>> + */
> >>> +#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>
> >>> +#include <linux/bitops.h>
> >>> +#include <linux/slab.h>
> >>> +#include <linux/workqueue.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 registering and unregistering the SR helpers on load/unload.
> >>> + *
> >>> + * Once a crtc has enabled SR, 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 a flag set in
> >>> + * state that tells you to disable/enable SR on the panel instead of power-
> >>> + * cycling it.
> >>> + *
> >>> + * Drivers may choose to fully disable their crtc/encoder/bridge hardware, or
> >>> + * they can use the "self_refresh_active" and "self_refresh_changed" flags in
> >>> + * object state 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 SR mode.
> >>> + */
> >>> +
> >>> +struct drm_self_refresh_state {
> >>> +   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_state *sr_state = container_of(
> >>> +                           to_delayed_work(work),
> >>> +                           struct drm_self_refresh_state, entry_work);
> >>> +   struct drm_crtc *crtc = sr_state->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;
> >>> +
> >>> +   crtc_state->active = false;
> >>> +   crtc_state->self_refresh_active = true;
> >>> +
> >>> +   for_each_new_connector_in_state(state, conn, conn_state, i) {
> >>> +           if (!conn_state->self_refresh_aware)
> >>> +                   goto out;
> >>> +
> >>> +           conn_state->self_refresh_changed = true;
> >>> +           conn_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) {
> >>> +           for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> >>> +                   if (crtc_state->self_refresh_active) {
> >>> +                           state->async_update = false;
> >>> +                           break;
> >>> +                   }
> >>> +           }
> >>> +   }
> >>> +   if (!state->allow_modeset) {
> >>> +           for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> >>> +                   if (crtc_state->self_refresh_active) {
> >>> +                           state->allow_modeset = true;
> >>> +                           break;
> >>> +                   }
> >>> +           }
> >>> +   }
> >>> +
> >>> +   for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
> >>> +           struct drm_self_refresh_state *sr_state;
> >>> +
> >>> +           /* Don't trigger the entry timer when we're already in SR */
> >>> +           if (crtc_state->self_refresh_active)
> >>> +                   continue;
> >>> +
> >>> +           sr_state = crtc->self_refresh_state;
> >>> +           mod_delayed_work(system_wq, &sr_state->entry_work,
> >>> +                    msecs_to_jiffies(sr_state->entry_delay_ms));
> >>> +   }
> >>> +}
> >>> +EXPORT_SYMBOL(drm_self_refresh_helper_alter_state);
> >>> +
> >>> +/**
> >>> + * drm_self_refresh_helper_register - Registers 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
> >>> + */
> >>> +int drm_self_refresh_helper_register(struct drm_crtc *crtc,
> >>> +                                unsigned int entry_delay_ms)
> >>> +{
> >>> +   struct drm_self_refresh_state *sr_state = crtc->self_refresh_state;
> >>> +
> >>> +   /* Helper is already registered */
> >>> +   if (WARN_ON(sr_state))
> >>> +           return -EINVAL;
> >>> +
> >>> +   sr_state = kzalloc(sizeof(*sr_state), GFP_KERNEL);
> >>> +   if (!sr_state)
> >>> +           return -ENOMEM;
> >>> +
> >>> +   INIT_DELAYED_WORK(&sr_state->entry_work,
> >>> +                     drm_self_refresh_helper_entry_work);
> >>> +   sr_state->entry_delay_ms = entry_delay_ms;
> >>> +   sr_state->crtc = crtc;
> >>> +
> >>> +   crtc->self_refresh_state = sr_state;
> >>> +   return 0;
> >>> +}
> >>> +EXPORT_SYMBOL(drm_self_refresh_helper_register);
> >>> +
> >>> +/**
> >>> + * drm_self_refresh_helper_unregister - Unregisters self refresh helpers
> >>> + * @crtc: the crtc to unregister
> >>> + */
> >>> +void drm_self_refresh_helper_unregister(struct drm_crtc *crtc)
> >>> +{
> >>> +   struct drm_self_refresh_state *sr_state = crtc->self_refresh_state;
> >>> +
> >>> +   /* Helper is already unregistered */
> >>> +   if (sr_state)
> >>> +           return;
> >>> +
> >>> +   crtc->self_refresh_state = NULL;
> >>> +
> >>> +   cancel_delayed_work_sync(&sr_state->entry_work);
> >>> +   kfree(sr_state);
> >>> +}
> >>> +EXPORT_SYMBOL(drm_self_refresh_helper_unregister);
> >>> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> >>> index 824a5ed4e216..1e9cab1da97a 100644
> >>> --- a/include/drm/drm_atomic.h
> >>> +++ b/include/drm/drm_atomic.h
> >>> @@ -944,4 +944,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_ */
> >>> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> >>> index c8061992d6cb..0ae7e812ec62 100644
> >>> --- a/include/drm/drm_connector.h
> >>> +++ b/include/drm/drm_connector.h
> >>> @@ -501,6 +501,37 @@ struct drm_connector_state {
> >>>     /** @tv: TV connector state */
> >>>     struct drm_tv_connector_state tv;
> >>>
> >>> +   /**
> >>> +    * @self_refresh_changed:
> >>> +    *
> >>> +    * Set true when self refresh status has changed. This is useful for
> >>> +    * use in encoder/bridge enable where the old state is unavailable to
> >>> +    * the driver and it needs to know whether the enable transition is a
> >>> +    * full transition, or if it just needs to exit self refresh mode.
> >>> +    */
> >>> +   bool self_refresh_changed;
> >>> +
> >>> +   /**
> >>> +    * @self_refresh_active:
> >>> +    *
> >>> +    * Used by the self refresh (SR) helpers to denote when the display
> >>> +    * should be self refreshing. If your connector is SR-capable, check
> >>> +    * this flag in .disable(). If it is true, instead of shutting off the
> >>> +    * panel, put it into self refreshing mode.
> >>> +    */
> >>> +   bool self_refresh_active;
> >>> +
> >>> +   /**
> >>> +    * @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.
> >>> +    */
> >>> +   bool self_refresh_aware;
> >>> +
> >>>     /**
> >>>      * @picture_aspect_ratio: Connector property to control the
> >>>      * HDMI infoframe aspect ratio setting.
> >>> diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
> >>> index f7c3022dbdf4..208d68129b4c 100644
> >>> --- a/include/drm/drm_crtc.h
> >>> +++ b/include/drm/drm_crtc.h
> >>> @@ -53,6 +53,7 @@ struct drm_mode_set;
> >>>  struct drm_file;
> >>>  struct drm_clip_rect;
> >>>  struct drm_printer;
> >>> +struct drm_self_refresh_state;
> >>>  struct device_node;
> >>>  struct dma_fence;
> >>>  struct edid;
> >>> @@ -299,6 +300,17 @@ struct drm_crtc_state {
> >>>      */
> >>>     bool vrr_enabled;
> >>>
> >>> +   /**
> >>> +    * @self_refresh_active:
> >>> +    *
> >>> +    * Used by the self refresh helpers to denote when a self refresh
> >>> +    * transition is occuring. This will be set on enable/disable callbacks
> >>> +    * when self refresh is being enabled or disabled. In some cases, it may
> >>> +    * not be desirable to fully shut off the crtc during self refresh.
> >>> +    * CRTC's can inspect this flag and determine the best course of action.
> >>> +    */
> >>> +   bool self_refresh_active;
> >>> +
> >>>     /**
> >>>      * @event:
> >>>      *
> >>> @@ -1087,6 +1099,13 @@ struct drm_crtc {
> >>>      * The name of the CRTC's fence timeline.
> >>>      */
> >>>     char timeline_name[32];
> >>> +
> >>> +   /**
> >>> +    * @self_refresh_state: Holds the state for the self refresh helpers
> >>> +    *
> >>> +    * Initialized via drm_self_refresh_helper_register().
> >>> +    */
> >>> +   struct drm_self_refresh_state *self_refresh_state;
> >>>  };
> >>>
> >>>  /**
> >>> diff --git a/include/drm/drm_self_refresh_helper.h b/include/drm/drm_self_refresh_helper.h
> >>> new file mode 100644
> >>> index 000000000000..015dacb8a807
> >>> --- /dev/null
> >>> +++ b/include/drm/drm_self_refresh_helper.h
> >>> @@ -0,0 +1,23 @@
> >>> +/* SPDX-License-Identifier: MIT */
> >>> +/*
> >>> + * Copyright (C) 2019 Google, Inc.
> >>> + *
> >>> + * Authors:
> >>> + * Sean Paul <seanpaul@chromium.org>
> >>> + */
> >>> +#ifndef DRM_SELF_REFRESH_HELPER_H_
> >>> +#define DRM_SELF_REFRESH_HELPER_H_
> >>> +
> >>> +struct drm_atomic_state;
> >>> +struct drm_connector;
> >>> +struct drm_device;
> >>> +struct drm_self_refresh_state;
> >>> +struct drm_modeset_acquire_ctx;
> >>> +
> >>> +void drm_self_refresh_helper_alter_state(struct drm_atomic_state *state);
> >>> +
> >>> +int drm_self_refresh_helper_register(struct drm_crtc *crtc,
> >>> +                                unsigned int entry_delay_ms);
> >>> +
> >>> +void drm_self_refresh_helper_unregister(struct drm_crtc *crtc);
> >>> +#endif
> >>>
> >>
> >> _______________________________________________
> >> dri-devel mailing list
> >> dri-devel@lists.freedesktop.org
> >> https://lists.freedesktop.org/mailman/listinfo/dri-devel
> >
>
Daniel Vetter April 2, 2019, 9:53 a.m. UTC | #15
On Tue, Mar 26, 2019 at 04:44:54PM -0400, Sean Paul wrote:
> From: Sean Paul <seanpaul@chromium.org>
> 
> 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' and/or
> 'self_refresh_changed' state in crtc_state and connector_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)
> 
> Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
> Signed-off-by: Sean Paul <seanpaul@chromium.org>
> ---
>  Documentation/gpu/drm-kms-helpers.rst     |   9 +
>  drivers/gpu/drm/Makefile                  |   3 +-
>  drivers/gpu/drm/drm_atomic.c              |   4 +
>  drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
>  drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
>  drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
>  drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
>  include/drm/drm_atomic.h                  |  15 ++
>  include/drm/drm_connector.h               |  31 ++++
>  include/drm/drm_crtc.h                    |  19 ++
>  include/drm/drm_self_refresh_helper.h     |  23 +++
>  11 files changed, 360 insertions(+), 5 deletions(-)
>  create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
>  create mode 100644 include/drm/drm_self_refresh_helper.h

Oversight of an entirely different area that I just realized (and which
routinely leads to pain in i915 self refresh trickery):

What about vblank and the vblank query ioctls?

Apps kinda expect those to keep ticking, for frame scheduling purposes,
but if we call into the crtc disable hooks, which should call into
drm_vblank_off, userspace will get an EINVAL. Which might trigger some
fallbacks, but not really what we want I think.

In i915 we're trying hard to keep the vblanks rolling, by estimating some
fake vblank count and timestamps, and then correcting when we restart the
pipe.

Also no idea how to fit that into helpers ... we might need a
drm_vblank_self_refresh. Also, probably needs immediate_disable mode,
which needs accurate timestamps. At least the tricks we do in i915.

No idea yet what to do.
-Daniel
> 
> diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
> index 58b375e47615..b0b71f73829f 100644
> --- a/Documentation/gpu/drm-kms-helpers.rst
> +++ b/Documentation/gpu/drm-kms-helpers.rst
> @@ -107,6 +107,15 @@ fbdev Helper Functions Reference
>  .. kernel-doc:: drivers/gpu/drm/drm_fb_helper.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:
> +
>  Framebuffer CMA Helper Functions Reference
>  ==========================================
>  
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index e630eccb951c..5b3a1e26ec94 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -38,7 +38,8 @@ drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_dsc.o drm_probe_helper
>  		drm_kms_helper_common.o drm_dp_dual_mode_helper.o \
>  		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_atomic_state_helper.o drm_damage_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
> diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> index 5eb40130fafb..a06fe55b5ebf 100644
> --- a/drivers/gpu/drm/drm_atomic.c
> +++ b/drivers/gpu/drm/drm_atomic.c
> @@ -379,6 +379,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);
> @@ -881,6 +882,9 @@ 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_active=%d\n", state->self_refresh_active);
> +	drm_printf(p, "\tself_refresh_aware=%d\n", state->self_refresh_aware);
> +	drm_printf(p, "\tself_refresh_changed=%d\n", state->self_refresh_changed);
>  
>  	if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
>  		if (state->writeback_job && state->writeback_job->fb)
> diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> index 2453678d1186..c659db105133 100644
> --- a/drivers/gpu/drm/drm_atomic_helper.c
> +++ b/drivers/gpu/drm/drm_atomic_helper.c
> @@ -30,6 +30,7 @@
>  #include <drm/drm_atomic_uapi.h>
>  #include <drm/drm_plane_helper.h>
>  #include <drm/drm_atomic_helper.h>
> +#include <drm/drm_self_refresh_helper.h>
>  #include <drm/drm_writeback.h>
>  #include <drm/drm_damage_helper.h>
>  #include <linux/dma-fence.h>
> @@ -950,10 +951,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)
>  {
> @@ -974,7 +998,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;
>  
> @@ -1018,7 +1049,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;
> @@ -2948,6 +2979,7 @@ int drm_atomic_helper_set_config(struct drm_mode_set *set,
>  		return -ENOMEM;
>  
>  	state->acquire_ctx = ctx;
> +
>  	ret = __drm_atomic_helper_set_config(set, state);
>  	if (ret != 0)
>  		goto fail;
> diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
> index 4985384e51f6..ec90c527deed 100644
> --- a/drivers/gpu/drm/drm_atomic_state_helper.c
> +++ b/drivers/gpu/drm/drm_atomic_state_helper.c
> @@ -105,6 +105,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);
>  
> @@ -370,6 +374,10 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
>  
>  	/* Don't copy over a writeback job, they are used only once */
>  	state->writeback_job = NULL;
> +
> +	/* Self refresh should be canceled when a new update is available */
> +	state->self_refresh_changed = state->self_refresh_active;
> +	state->self_refresh_active = false;
>  }
>  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
>  
> diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
> index 4eb81f10bc54..d2085332172b 100644
> --- a/drivers/gpu/drm/drm_atomic_uapi.c
> +++ b/drivers/gpu/drm/drm_atomic_uapi.c
> @@ -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)
> @@ -785,7 +785,8 @@ 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;
> +		*val = state->self_refresh_active ? DRM_MODE_DPMS_ON :
> +			connector->dpms;
>  	} else if (property == config->tv_select_subconnector_property) {
>  		*val = state->tv.subconnector;
>  	} else if (property == config->tv_left_margin_property) {
> diff --git a/drivers/gpu/drm/drm_self_refresh_helper.c b/drivers/gpu/drm/drm_self_refresh_helper.c
> new file mode 100644
> index 000000000000..a3afa031480e
> --- /dev/null
> +++ b/drivers/gpu/drm/drm_self_refresh_helper.c
> @@ -0,0 +1,212 @@
> +/* SPDX-License-Identifier: MIT */
> +/*
> + * Copyright (C) 2019 Google, Inc.
> + *
> + * Authors:
> + * Sean Paul <seanpaul@chromium.org>
> + */
> +#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>
> +#include <linux/bitops.h>
> +#include <linux/slab.h>
> +#include <linux/workqueue.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 registering and unregistering the SR helpers on load/unload.
> + *
> + * Once a crtc has enabled SR, 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 a flag set in
> + * state that tells you to disable/enable SR on the panel instead of power-
> + * cycling it.
> + *
> + * Drivers may choose to fully disable their crtc/encoder/bridge hardware, or
> + * they can use the "self_refresh_active" and "self_refresh_changed" flags in
> + * object state 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 SR mode.
> + */
> +
> +struct drm_self_refresh_state {
> +	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_state *sr_state = container_of(
> +				to_delayed_work(work),
> +				struct drm_self_refresh_state, entry_work);
> +	struct drm_crtc *crtc = sr_state->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;
> +
> +	crtc_state->active = false;
> +	crtc_state->self_refresh_active = true;
> +
> +	for_each_new_connector_in_state(state, conn, conn_state, i) {
> +		if (!conn_state->self_refresh_aware)
> +			goto out;
> +
> +		conn_state->self_refresh_changed = true;
> +		conn_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) {
> +		for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> +			if (crtc_state->self_refresh_active) {
> +				state->async_update = false;
> +				break;
> +			}
> +		}
> +	}
> +	if (!state->allow_modeset) {
> +		for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> +			if (crtc_state->self_refresh_active) {
> +				state->allow_modeset = true;
> +				break;
> +			}
> +		}
> +	}
> +
> +	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
> +		struct drm_self_refresh_state *sr_state;
> +
> +		/* Don't trigger the entry timer when we're already in SR */
> +		if (crtc_state->self_refresh_active)
> +			continue;
> +
> +		sr_state = crtc->self_refresh_state;
> +		mod_delayed_work(system_wq, &sr_state->entry_work,
> +			 msecs_to_jiffies(sr_state->entry_delay_ms));
> +	}
> +}
> +EXPORT_SYMBOL(drm_self_refresh_helper_alter_state);
> +
> +/**
> + * drm_self_refresh_helper_register - Registers 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
> + */
> +int drm_self_refresh_helper_register(struct drm_crtc *crtc,
> +				     unsigned int entry_delay_ms)
> +{
> +	struct drm_self_refresh_state *sr_state = crtc->self_refresh_state;
> +
> +	/* Helper is already registered */
> +	if (WARN_ON(sr_state))
> +		return -EINVAL;
> +
> +	sr_state = kzalloc(sizeof(*sr_state), GFP_KERNEL);
> +	if (!sr_state)
> +		return -ENOMEM;
> +
> +	INIT_DELAYED_WORK(&sr_state->entry_work,
> +			  drm_self_refresh_helper_entry_work);
> +	sr_state->entry_delay_ms = entry_delay_ms;
> +	sr_state->crtc = crtc;
> +
> +	crtc->self_refresh_state = sr_state;
> +	return 0;
> +}
> +EXPORT_SYMBOL(drm_self_refresh_helper_register);
> +
> +/**
> + * drm_self_refresh_helper_unregister - Unregisters self refresh helpers
> + * @crtc: the crtc to unregister
> + */
> +void drm_self_refresh_helper_unregister(struct drm_crtc *crtc)
> +{
> +	struct drm_self_refresh_state *sr_state = crtc->self_refresh_state;
> +
> +	/* Helper is already unregistered */
> +	if (sr_state)
> +		return;
> +
> +	crtc->self_refresh_state = NULL;
> +
> +	cancel_delayed_work_sync(&sr_state->entry_work);
> +	kfree(sr_state);
> +}
> +EXPORT_SYMBOL(drm_self_refresh_helper_unregister);
> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> index 824a5ed4e216..1e9cab1da97a 100644
> --- a/include/drm/drm_atomic.h
> +++ b/include/drm/drm_atomic.h
> @@ -944,4 +944,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_ */
> diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> index c8061992d6cb..0ae7e812ec62 100644
> --- a/include/drm/drm_connector.h
> +++ b/include/drm/drm_connector.h
> @@ -501,6 +501,37 @@ struct drm_connector_state {
>  	/** @tv: TV connector state */
>  	struct drm_tv_connector_state tv;
>  
> +	/**
> +	 * @self_refresh_changed:
> +	 *
> +	 * Set true when self refresh status has changed. This is useful for
> +	 * use in encoder/bridge enable where the old state is unavailable to
> +	 * the driver and it needs to know whether the enable transition is a
> +	 * full transition, or if it just needs to exit self refresh mode.
> +	 */
> +	bool self_refresh_changed;
> +
> +	/**
> +	 * @self_refresh_active:
> +	 *
> +	 * Used by the self refresh (SR) helpers to denote when the display
> +	 * should be self refreshing. If your connector is SR-capable, check
> +	 * this flag in .disable(). If it is true, instead of shutting off the
> +	 * panel, put it into self refreshing mode.
> +	 */
> +	bool self_refresh_active;
> +
> +	/**
> +	 * @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.
> +	 */
> +	bool self_refresh_aware;
> +
>  	/**
>  	 * @picture_aspect_ratio: Connector property to control the
>  	 * HDMI infoframe aspect ratio setting.
> diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
> index f7c3022dbdf4..208d68129b4c 100644
> --- a/include/drm/drm_crtc.h
> +++ b/include/drm/drm_crtc.h
> @@ -53,6 +53,7 @@ struct drm_mode_set;
>  struct drm_file;
>  struct drm_clip_rect;
>  struct drm_printer;
> +struct drm_self_refresh_state;
>  struct device_node;
>  struct dma_fence;
>  struct edid;
> @@ -299,6 +300,17 @@ struct drm_crtc_state {
>  	 */
>  	bool vrr_enabled;
>  
> +	/**
> +	 * @self_refresh_active:
> +	 *
> +	 * Used by the self refresh helpers to denote when a self refresh
> +	 * transition is occuring. This will be set on enable/disable callbacks
> +	 * when self refresh is being enabled or disabled. In some cases, it may
> +	 * not be desirable to fully shut off the crtc during self refresh.
> +	 * CRTC's can inspect this flag and determine the best course of action.
> +	 */
> +	bool self_refresh_active;
> +
>  	/**
>  	 * @event:
>  	 *
> @@ -1087,6 +1099,13 @@ struct drm_crtc {
>  	 * The name of the CRTC's fence timeline.
>  	 */
>  	char timeline_name[32];
> +
> +	/**
> +	 * @self_refresh_state: Holds the state for the self refresh helpers
> +	 *
> +	 * Initialized via drm_self_refresh_helper_register().
> +	 */
> +	struct drm_self_refresh_state *self_refresh_state;
>  };
>  
>  /**
> diff --git a/include/drm/drm_self_refresh_helper.h b/include/drm/drm_self_refresh_helper.h
> new file mode 100644
> index 000000000000..015dacb8a807
> --- /dev/null
> +++ b/include/drm/drm_self_refresh_helper.h
> @@ -0,0 +1,23 @@
> +/* SPDX-License-Identifier: MIT */
> +/*
> + * Copyright (C) 2019 Google, Inc.
> + *
> + * Authors:
> + * Sean Paul <seanpaul@chromium.org>
> + */
> +#ifndef DRM_SELF_REFRESH_HELPER_H_
> +#define DRM_SELF_REFRESH_HELPER_H_
> +
> +struct drm_atomic_state;
> +struct drm_connector;
> +struct drm_device;
> +struct drm_self_refresh_state;
> +struct drm_modeset_acquire_ctx;
> +
> +void drm_self_refresh_helper_alter_state(struct drm_atomic_state *state);
> +
> +int drm_self_refresh_helper_register(struct drm_crtc *crtc,
> +				     unsigned int entry_delay_ms);
> +
> +void drm_self_refresh_helper_unregister(struct drm_crtc *crtc);
> +#endif
> -- 
> Sean Paul, Software Engineer, Google / Chromium OS
>
Sean Paul April 2, 2019, 1:24 p.m. UTC | #16
On Tue, Apr 02, 2019 at 09:49:00AM +0200, Daniel Vetter wrote:
> On Mon, Apr 01, 2019 at 09:49:30AM -0400, Sean Paul wrote:
> > On Fri, Mar 29, 2019 at 08:21:31PM +0100, Daniel Vetter wrote:
> > > On Fri, Mar 29, 2019 at 7:10 PM Sean Paul <sean@poorly.run> wrote:
> > > >
> > > > On Fri, Mar 29, 2019 at 04:36:32PM +0100, Daniel Vetter wrote:
> > > > > On Fri, Mar 29, 2019 at 09:16:59AM -0400, Sean Paul wrote:
> > > > > > On Fri, Mar 29, 2019 at 09:21:10AM +0100, Daniel Vetter wrote:
> > > > > > > On Thu, Mar 28, 2019 at 05:03:03PM -0400, Sean Paul wrote:
> > > > > > > > On Wed, Mar 27, 2019 at 07:15:00PM +0100, Daniel Vetter wrote:
> > > > > > > > > On Tue, Mar 26, 2019 at 04:44:54PM -0400, Sean Paul wrote:
> > > > > > > > > > From: Sean Paul <seanpaul@chromium.org>
> > > > > > > > > >
> > > > > > > > > > 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' and/or
> > > > > > > > > > 'self_refresh_changed' state in crtc_state and connector_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)
> > > > > > > > > >
> > > > > > > > > > Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
> > > > > > > > > > Signed-off-by: Sean Paul <seanpaul@chromium.org>
> > > > > > > > > > ---
> > > > > > > > > >  Documentation/gpu/drm-kms-helpers.rst     |   9 +
> > > > > > > > > >  drivers/gpu/drm/Makefile                  |   3 +-
> > > > > > > > > >  drivers/gpu/drm/drm_atomic.c              |   4 +
> > > > > > > > > >  drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
> > > > > > > > > >  drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
> > > > > > > > > >  drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
> > > > > > > > > >  drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
> > > > > > > > > >  include/drm/drm_atomic.h                  |  15 ++
> > > > > > > > > >  include/drm/drm_connector.h               |  31 ++++
> > > > > > > > > >  include/drm/drm_crtc.h                    |  19 ++
> > > > > > > > > >  include/drm/drm_self_refresh_helper.h     |  23 +++
> > > > > > > > > >  11 files changed, 360 insertions(+), 5 deletions(-)
> > > > > > > > > >  create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
> > > > > > > > > >  create mode 100644 include/drm/drm_self_refresh_helper.h
> > > > > > > > > >
> > > > > >
> > > > > > /snip
> > > > > >
> > > > > > > > > > index 4985384e51f6..ec90c527deed 100644
> > > > > > > > > > --- a/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > > > > > > +++ b/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > > > > > > @@ -105,6 +105,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);
> > > > > > > > > >
> > > > > > > > > > @@ -370,6 +374,10 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> > > > > > > > > >
> > > > > > > > > >     /* Don't copy over a writeback job, they are used only once */
> > > > > > > > > >     state->writeback_job = NULL;
> > > > > > > > > > +
> > > > > > > > > > +   /* Self refresh should be canceled when a new update is available */
> > > > > > > > > > +   state->self_refresh_changed = state->self_refresh_active;
> > > > > > > > > > +   state->self_refresh_active = false;
> > > > > > > > >
> > > > > > > > > Why the duplication in self-refresh tracking? Connectors never have a
> > > > > > > > > different self-refresh state, and you can always look at the right
> > > > > > > > > crtc_state. Duplication just gives us the chance to screw up and get out
> > > > > > > > > of sync (e.g. if the crtc for a connector changes).
> > > > > > > > >
> > > > > > > >
> > > > > > > > On disable the crtc is cleared from connector_state, so we don't have access to
> > > > > > > > it. If I add the appropriate atomic_enable/disable hooks as suggested below, we
> > > > > > > > should be able to nuke these.
> > > > > > >
> > > > > > > Yeah we'd need the old state to look at the crtc and all that. Which is a
> > > > > > > lot more trickier.
> > > > > > >
> > > > > > > Since it's such a special case, should we have a dedicated callback for
> > > > > > > the direct self-refresh -> completely off transition? It'll be asymetric,
> > > > > > > but that's the nature of this I think.
> > > > > >
> > > > > > Right, the asymmetry is really annoying here. If the driver is SR-aware, it makes
> > > > > > sense since SR-active to disable is a real transition. However if the driver is
> > > > > > not SR-aware (ie: it just gets turned off when SR becomes active), the disable
> > > > > > function gets called twice without an enable. So that changes the "for every
> > > > > > enable there is a disable and vice versa" assumption.
> > > > > >
> > > > > > This is one of the benefits of the v1 design, SR was bolted on and no existing
> > > > > > rules (async/no_modeset/enable-disable pairs) were [explicitly] broken. That's
> > > > > > not to say it was better, it wasn't, but it was a big consideration.
> > > > > >
> > > > > > So, what to do.
> > > > > >
> > > > > > I really like the idea that drivers shouldn't have to be SR-aware to be involved
> > > > > > in the pipeline. So if we add a hook for this like you suggest, we could avoid
> > > > > > calling disable twice on anything not SR-aware. We would need to add the hook on
> > > > > > crtc/encoder/bridge to make sure you could mix n' match SR-aware and
> > > > > > non-SR-aware devices.
> > > > > >
> > > > > > It probably makes sense to just add matching SR hooks at this point. Since if
> > > > > > the driver is doing something special in disable, it'll need to do something
> > > > > > special in enable. It also reserves enable and disable for what they've
> > > > > > traditionally done. If a device is not SR-aware, it'll just fall back to the
> > > > > > full enable/disable and we'll make sure to not double up on the disable in the
> > > > > > helpers.
> > > > > >
> > > > > > So we'll keep symmetry, and avoid having an awful hook name like
> > > > > > disable_from_self_refresh.. yuck!
> > > > > >
> > > > > > Thoughts?
> > > > >
> > > > > I like the asymetry actually, it has grown on a bit while working out and
> > > > > pondering this :-)
> > > > >
> > > >
> > > > I'm not quite there with you, I still think it's better to split it all out.
> > > >
> > > > > Benefits:
> > > > > - we keep the 100% symmetry of enable/disable hooks
> > > > > - self-refresh aware connector code also gets a bit simpler I think: in
> > > > >   the normal enable/disable hooks it can just check for
> > > > >   connector->state->crtc->state->self_refresh_active for sr state changes
> > > > >   while the pipe is logically staying on
> > > > > - the one asymmetric case due to this design where we disable the pipe
> > > > >   harder has an awkward special hook, which gives us a great opportunity
> > > > >   to explain why it's needed
> > > > > - nothing changes for non-sr aware drivers
> > > > > - also no need to duplicate sr state into connectors, since it's all
> > > > >   fairly explit already in all three state transitions.
> > > >
> > > > To be fair, only one of these is exclusive to asymmetry, and it's the one that
> > > > provides the opportunity to add a comment. If the sr functions are symmetric,
> > > > the code becomes much more "normal" and less deserving of the explanation.
> > > >
> > > > The reason I would like to split out entry and exit is that it makes the driver
> > > > code a bit easier to rationalize. Currently we need to check the state at the
> > > > beginning of enable/disable to determine whether we want the full enable/disable
> > > > or the psr exit/enter. So the sr_disable function would really just be plain
> > > > old disable without the special casing at the top. In that case, we don't even
> > > > need the separate function, we could just limit disable calls only on those
> > > > objects which are effectively on (active || sr). That starts sounding a lot like
> > > > what we already have here.
> > > >
> > > > Further, doing SR in enable/disable is really just legacy from v1 which tried to
> > > > keep as much the same as possible. Now that we're "in it", I think it makes
> > > > sense to go all in and make SR a first class citizen.
> > > 
> > > Hm, question is: How many hooks do you need? Just something on the
> > > connector, or on the encoder, or everywhere?
> > 
> > bridge/encoder/crtc all do special things during SR transitions, I don't think
> > connector is necessary. This is the same for any .sr_disable function, everyone
> > would need to implement it.
> 
> Hm, that's a lot of new callbacks ...
> 
> > > And how do you handle the
> > > various state transitions. On the disable side we have:
> > > - active on -> active off, no sr (userspace disables crtc)
> > > - active on, sr off -> active ooff, sr on (sr timer fires and suspends crtc)
> > > - active off, sr on -> active off, sr off (userspace disable crtc
> > > while crtc is in sr)
> > > These are all "logical active on" -> "something" transitions where we
> > > disable something (crtc, or display or both)
> > > 
> > > So in a way you'd need 3 hooks here for the full matrix.
> > > And they all
> > > kinda disable something. On the enable side we have:
> > > - active off, sr off -> active on, sr off (userspace enables crtc)
> > > - active off, sr on -> active on, sr off (userspace does a pageflip, stops sr)
> > > Here we either enable the crtc (display already on) or both. Since we
> > > only go into sr with the timer there's no 3rd case of only enabling
> > > the display. So still asymetric, even with lots more hooks.
> > 
> > We don't need the (active off, sr on) -> (active off, sr off) (third) case
> > above, it's the same as the first. Just doing a full disable is sufficient,
> > so you would have symmetry in the enable/disable calls and asymmetry in the
> > sr calls. This is similar to enabling a plane, or turning other HW features on
> > while enabled. SR is after all just a feature of the hardware.
> 
> Hm yeah I guess we can treat it like plane disabling, which implicitly
> happens in crtc->disable too. Or the implicit plane enable in crtc->enable
> (although that case doesn't exist for sr, since we never go directly into
> sr).
> 
> > > If you want the full matrix, there's going to be a _lot_ of hooks. I
> > > think slightly more awkward driver, but less hooks is better. Hence
> > > the slightly awkward middle ground of a special disable_from_sr hook.
> > > But maybe there's a better option somewhere else ...
> > 
> > There's really no reason to even have the sr_disable function. The .disable
> > function in the driver will already need special casing to detect psr_entry
> > vs full disable, so it'd be better to just call disable twice. The .sr_disable
> > function would always just do a full disable (ie: the .disable implementation
> > without the sr checks at the top).
> > 
> > So the debate should be: add sr_enable/disable pair of hooks, or overload
> > disable with asymmetry (current implementation).
> 
> I guess that means we're back to no new hooks, and the driver just dtrt
> in the existing hooks with the state transition bits we have? I thought
> the issue with that is that we can't get at all the right bits, hence the
> sr_disable special case hook.
> 
> Or is your plan to roll out a full new set of hooks, equipped with
> old/new_state for everything? I think we'd only need old/new_state for the
> object at hand, since with the old_state you can get at drm_atomic_state,
> which allows you to get anything else really.

I don't think we even need to pass the state to the sr hooks, just add

void self_refresh_enter(struct drm_<type> *<name>);
void self_refresh_exit(struct drm_<type> *<name>);

to the funcs vtable for crtc/encoder/bridge.

Of course it's not _quite_ as straightforward as that :)

With the current model, the powerdown/powerup order of components is implicitly
broken. With this new model, it's much more obvious, this is easiest to
illustrate with bridges, but it's true for crtcs and encoders as well.

Assume you have the following bridge chain:

ENC0 (not SR-aware) 
        -> BR0 (SR-aware)
                -> BR1 (not SR-aware)
                        -> BR2 (SR-aware)
                                -> CON0

An SR-enter transition would be:
        BR2->self_refresh_enter
        BR1->disable
        BR0->self_refresh_enter
        ENC0->disable
        BR1->post_disable

SR-exit is:
        BR1->pre_enable
        ENC0->enable
        BR0->self_refresh_exit
        BR1->enable
        BR2->self_refresh_exit

Disabling from SR becomes:
        BR2->disable
        BR0->disable
        BR2->post_disable
        BR0->post_disable

So I'm starting to question falling back on disable. I think it was a fine
choice when we would exit psr before disable (ie: v1), but I think it might be
too complicated now. We could make BR2 and BR0 do the right thing on
disable-from-SR, but I'm worried that mixing up the order for SR-unaware devices
(ENC0/BR1) might cause issues.

Perhaps we should scale this back and just treat self_refresh as its own thing
and not go through the enable/disable path at all. Devices which are not
SR-aware stay on (which has it's own issues if BR1 underflows because it's
expecting video from BR0). Maybe we have to ensure the entire pipe is SR-aware
before we do an SR-enter.

Thoughts?

Sean

> 
> Or should we just add drm_atomic_state *state to all these hooks? That'd
> probably the most flexible long-term thing. Could even be done with cocci,
> so we don't need new atomic_disable2 calls and silly things like that.
> -Daniel
> > 
> > Sean
> > 
> > > -Daniel
> > > 
> > > >
> > > > Sean
> > > >
> > > > >
> > > > > - SR on can only happen if the logical crtc_state->active is on and stays on
> > > > > - SR can get disabled in 2 subcases
> > > > >   - logical active state stays on -> handled with existing hooks
> > > > >   - logical active state also goes off -> existing hooks all skip (because
> > > > >     active=false -> active=false is a no-op), the special ->sr_disable
> > > > >     takes care
> > > > >
> > > > > It feels like this is clean, integrates well with atomic helpers overall
> > > > > and it even makes sense. At least to my slightly oxygen deprived mind
> > > > > right now ...
> > > > >
> > > > > > > > > >  }
> > > > > > > > > >  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
> > > > > > > > > >
> > > > > >
> > > > > > /snip
> > > > > >
> > > > > > > > > > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> > > > > > > > > > index c8061992d6cb..0ae7e812ec62 100644
> > > > > > > > > > --- a/include/drm/drm_connector.h
> > > > > > > > > > +++ b/include/drm/drm_connector.h
> > > > > > > > > > @@ -501,6 +501,37 @@ struct drm_connector_state {
> > > > > > > > > >     /** @tv: TV connector state */
> > > > > > > > > >     struct drm_tv_connector_state tv;
> > > > > > > > > >
> > > > > > > > > > +   /**
> > > > > > > > > > +    * @self_refresh_changed:
> > > > > > > > > > +    *
> > > > > > > > > > +    * Set true when self refresh status has changed. This is useful for
> > > > > > > > > > +    * use in encoder/bridge enable where the old state is unavailable to
> > > > > > > > > > +    * the driver and it needs to know whether the enable transition is a
> > > > > > > > > > +    * full transition, or if it just needs to exit self refresh mode.
> > > > > > > > >
> > > > > > > > > Uh, we just need proper atomic callbacks with all the states available.
> > > > > > > > > Once you have one, you can get at the others.
> > > > > > > > >
> > > > > > > >
> > > > > > > > Well, sure, we could do that too :)
> > > > > > >
> > > > > > > tbh I'm not sure whether that's really better, the duplication just irks
> > > > > > > me. With a new callback for the special self-refresh disable (I guess we
> > > > > > > only need that on the connector), plus looking at
> > > > > > > connector->state->crtc->state->self_refresh, I think we'd be covered
> > > > > > > as-is? Or is there a corner case I'm still missing?
> > > > > > >
> > > > > >
> > > > > > I think we can remove self_refresh_changed/self_refresh_active if we implement
> > > > > > dedicated hooks for self_refresh_enter/exit. We'll want to keep
> > > > > > self_refresh_aware around since the presence of the callback implementations
> > > > > > does not imply the panel connected supports SR.
> > > > >
> > > > > Yup, self_refresh_aware is needed.
> > > > >
> > > > > > As mentioned above, we'll need these hooks on everything in the pipeline to be
> > > > > > fully covered.
> > > > >
> > > > > Let's just do the ->sr_disable hook for now. I don't think we need all the
> > > > > others really.
> > > > >
> > > > > Cheers, Daniel
> > > > > --
> > > > > Daniel Vetter
> > > > > Software Engineer, Intel Corporation
> > > > > http://blog.ffwll.ch
> > > >
> > > > --
> > > > Sean Paul, Software Engineer, Google / Chromium OS
> > > 
> > > 
> > > 
> > > -- 
> > > Daniel Vetter
> > > Software Engineer, Intel Corporation
> > > +41 (0) 79 365 57 48 - http://blog.ffwll.ch
> > 
> > -- 
> > Sean Paul, Software Engineer, Google / Chromium OS
> 
> -- 
> Daniel Vetter
> Software Engineer, Intel Corporation
> http://blog.ffwll.ch
Ville Syrjälä April 2, 2019, 2:16 p.m. UTC | #17
On Tue, Apr 02, 2019 at 09:49:00AM +0200, Daniel Vetter wrote:
> On Mon, Apr 01, 2019 at 09:49:30AM -0400, Sean Paul wrote:
> > On Fri, Mar 29, 2019 at 08:21:31PM +0100, Daniel Vetter wrote:
> > > On Fri, Mar 29, 2019 at 7:10 PM Sean Paul <sean@poorly.run> wrote:
> > > >
> > > > On Fri, Mar 29, 2019 at 04:36:32PM +0100, Daniel Vetter wrote:
> > > > > On Fri, Mar 29, 2019 at 09:16:59AM -0400, Sean Paul wrote:
> > > > > > On Fri, Mar 29, 2019 at 09:21:10AM +0100, Daniel Vetter wrote:
> > > > > > > On Thu, Mar 28, 2019 at 05:03:03PM -0400, Sean Paul wrote:
> > > > > > > > On Wed, Mar 27, 2019 at 07:15:00PM +0100, Daniel Vetter wrote:
> > > > > > > > > On Tue, Mar 26, 2019 at 04:44:54PM -0400, Sean Paul wrote:
> > > > > > > > > > From: Sean Paul <seanpaul@chromium.org>
> > > > > > > > > >
> > > > > > > > > > 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' and/or
> > > > > > > > > > 'self_refresh_changed' state in crtc_state and connector_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)
> > > > > > > > > >
> > > > > > > > > > Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
> > > > > > > > > > Signed-off-by: Sean Paul <seanpaul@chromium.org>
> > > > > > > > > > ---
> > > > > > > > > >  Documentation/gpu/drm-kms-helpers.rst     |   9 +
> > > > > > > > > >  drivers/gpu/drm/Makefile                  |   3 +-
> > > > > > > > > >  drivers/gpu/drm/drm_atomic.c              |   4 +
> > > > > > > > > >  drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
> > > > > > > > > >  drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
> > > > > > > > > >  drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
> > > > > > > > > >  drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
> > > > > > > > > >  include/drm/drm_atomic.h                  |  15 ++
> > > > > > > > > >  include/drm/drm_connector.h               |  31 ++++
> > > > > > > > > >  include/drm/drm_crtc.h                    |  19 ++
> > > > > > > > > >  include/drm/drm_self_refresh_helper.h     |  23 +++
> > > > > > > > > >  11 files changed, 360 insertions(+), 5 deletions(-)
> > > > > > > > > >  create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
> > > > > > > > > >  create mode 100644 include/drm/drm_self_refresh_helper.h
> > > > > > > > > >
> > > > > >
> > > > > > /snip
> > > > > >
> > > > > > > > > > index 4985384e51f6..ec90c527deed 100644
> > > > > > > > > > --- a/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > > > > > > +++ b/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > > > > > > @@ -105,6 +105,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);
> > > > > > > > > >
> > > > > > > > > > @@ -370,6 +374,10 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> > > > > > > > > >
> > > > > > > > > >     /* Don't copy over a writeback job, they are used only once */
> > > > > > > > > >     state->writeback_job = NULL;
> > > > > > > > > > +
> > > > > > > > > > +   /* Self refresh should be canceled when a new update is available */
> > > > > > > > > > +   state->self_refresh_changed = state->self_refresh_active;
> > > > > > > > > > +   state->self_refresh_active = false;
> > > > > > > > >
> > > > > > > > > Why the duplication in self-refresh tracking? Connectors never have a
> > > > > > > > > different self-refresh state, and you can always look at the right
> > > > > > > > > crtc_state. Duplication just gives us the chance to screw up and get out
> > > > > > > > > of sync (e.g. if the crtc for a connector changes).
> > > > > > > > >
> > > > > > > >
> > > > > > > > On disable the crtc is cleared from connector_state, so we don't have access to
> > > > > > > > it. If I add the appropriate atomic_enable/disable hooks as suggested below, we
> > > > > > > > should be able to nuke these.
> > > > > > >
> > > > > > > Yeah we'd need the old state to look at the crtc and all that. Which is a
> > > > > > > lot more trickier.
> > > > > > >
> > > > > > > Since it's such a special case, should we have a dedicated callback for
> > > > > > > the direct self-refresh -> completely off transition? It'll be asymetric,
> > > > > > > but that's the nature of this I think.
> > > > > >
> > > > > > Right, the asymmetry is really annoying here. If the driver is SR-aware, it makes
> > > > > > sense since SR-active to disable is a real transition. However if the driver is
> > > > > > not SR-aware (ie: it just gets turned off when SR becomes active), the disable
> > > > > > function gets called twice without an enable. So that changes the "for every
> > > > > > enable there is a disable and vice versa" assumption.
> > > > > >
> > > > > > This is one of the benefits of the v1 design, SR was bolted on and no existing
> > > > > > rules (async/no_modeset/enable-disable pairs) were [explicitly] broken. That's
> > > > > > not to say it was better, it wasn't, but it was a big consideration.
> > > > > >
> > > > > > So, what to do.
> > > > > >
> > > > > > I really like the idea that drivers shouldn't have to be SR-aware to be involved
> > > > > > in the pipeline. So if we add a hook for this like you suggest, we could avoid
> > > > > > calling disable twice on anything not SR-aware. We would need to add the hook on
> > > > > > crtc/encoder/bridge to make sure you could mix n' match SR-aware and
> > > > > > non-SR-aware devices.
> > > > > >
> > > > > > It probably makes sense to just add matching SR hooks at this point. Since if
> > > > > > the driver is doing something special in disable, it'll need to do something
> > > > > > special in enable. It also reserves enable and disable for what they've
> > > > > > traditionally done. If a device is not SR-aware, it'll just fall back to the
> > > > > > full enable/disable and we'll make sure to not double up on the disable in the
> > > > > > helpers.
> > > > > >
> > > > > > So we'll keep symmetry, and avoid having an awful hook name like
> > > > > > disable_from_self_refresh.. yuck!
> > > > > >
> > > > > > Thoughts?
> > > > >
> > > > > I like the asymetry actually, it has grown on a bit while working out and
> > > > > pondering this :-)
> > > > >
> > > >
> > > > I'm not quite there with you, I still think it's better to split it all out.
> > > >
> > > > > Benefits:
> > > > > - we keep the 100% symmetry of enable/disable hooks
> > > > > - self-refresh aware connector code also gets a bit simpler I think: in
> > > > >   the normal enable/disable hooks it can just check for
> > > > >   connector->state->crtc->state->self_refresh_active for sr state changes
> > > > >   while the pipe is logically staying on
> > > > > - the one asymmetric case due to this design where we disable the pipe
> > > > >   harder has an awkward special hook, which gives us a great opportunity
> > > > >   to explain why it's needed
> > > > > - nothing changes for non-sr aware drivers
> > > > > - also no need to duplicate sr state into connectors, since it's all
> > > > >   fairly explit already in all three state transitions.
> > > >
> > > > To be fair, only one of these is exclusive to asymmetry, and it's the one that
> > > > provides the opportunity to add a comment. If the sr functions are symmetric,
> > > > the code becomes much more "normal" and less deserving of the explanation.
> > > >
> > > > The reason I would like to split out entry and exit is that it makes the driver
> > > > code a bit easier to rationalize. Currently we need to check the state at the
> > > > beginning of enable/disable to determine whether we want the full enable/disable
> > > > or the psr exit/enter. So the sr_disable function would really just be plain
> > > > old disable without the special casing at the top. In that case, we don't even
> > > > need the separate function, we could just limit disable calls only on those
> > > > objects which are effectively on (active || sr). That starts sounding a lot like
> > > > what we already have here.
> > > >
> > > > Further, doing SR in enable/disable is really just legacy from v1 which tried to
> > > > keep as much the same as possible. Now that we're "in it", I think it makes
> > > > sense to go all in and make SR a first class citizen.
> > > 
> > > Hm, question is: How many hooks do you need? Just something on the
> > > connector, or on the encoder, or everywhere?
> > 
> > bridge/encoder/crtc all do special things during SR transitions, I don't think
> > connector is necessary. This is the same for any .sr_disable function, everyone
> > would need to implement it.
> 
> Hm, that's a lot of new callbacks ...
> 
> > > And how do you handle the
> > > various state transitions. On the disable side we have:
> > > - active on -> active off, no sr (userspace disables crtc)
> > > - active on, sr off -> active ooff, sr on (sr timer fires and suspends crtc)
> > > - active off, sr on -> active off, sr off (userspace disable crtc
> > > while crtc is in sr)
> > > These are all "logical active on" -> "something" transitions where we
> > > disable something (crtc, or display or both)
> > > 
> > > So in a way you'd need 3 hooks here for the full matrix.
> > > And they all
> > > kinda disable something. On the enable side we have:
> > > - active off, sr off -> active on, sr off (userspace enables crtc)
> > > - active off, sr on -> active on, sr off (userspace does a pageflip, stops sr)
> > > Here we either enable the crtc (display already on) or both. Since we
> > > only go into sr with the timer there's no 3rd case of only enabling
> > > the display. So still asymetric, even with lots more hooks.
> > 
> > We don't need the (active off, sr on) -> (active off, sr off) (third) case
> > above, it's the same as the first. Just doing a full disable is sufficient,
> > so you would have symmetry in the enable/disable calls and asymmetry in the
> > sr calls. This is similar to enabling a plane, or turning other HW features on
> > while enabled. SR is after all just a feature of the hardware.
> 
> Hm yeah I guess we can treat it like plane disabling, which implicitly
> happens in crtc->disable too. Or the implicit plane enable in crtc->enable
> (although that case doesn't exist for sr, since we never go directly into
> sr).
> 
> > > If you want the full matrix, there's going to be a _lot_ of hooks. I
> > > think slightly more awkward driver, but less hooks is better. Hence
> > > the slightly awkward middle ground of a special disable_from_sr hook.
> > > But maybe there's a better option somewhere else ...
> > 
> > There's really no reason to even have the sr_disable function. The .disable
> > function in the driver will already need special casing to detect psr_entry
> > vs full disable, so it'd be better to just call disable twice. The .sr_disable
> > function would always just do a full disable (ie: the .disable implementation
> > without the sr checks at the top).
> > 
> > So the debate should be: add sr_enable/disable pair of hooks, or overload
> > disable with asymmetry (current implementation).
> 
> I guess that means we're back to no new hooks, and the driver just dtrt
> in the existing hooks with the state transition bits we have? I thought
> the issue with that is that we can't get at all the right bits, hence the
> sr_disable special case hook.
> 
> Or is your plan to roll out a full new set of hooks, equipped with
> old/new_state for everything? I think we'd only need old/new_state for the
> object at hand, since with the old_state you can get at drm_atomic_state,
> which allows you to get anything else really.

I would suggest all new hooks should be specified as
do_stuff(struct drm_atomic_state *state, struct drm_foo *foo);
That way there is less confusion how to get at other states
besides the old/new foo states explicitly passed in.
Daniel Vetter April 2, 2019, 4:05 p.m. UTC | #18
On Tue, Apr 2, 2019 at 3:24 PM Sean Paul <sean@poorly.run> wrote:
>
> On Tue, Apr 02, 2019 at 09:49:00AM +0200, Daniel Vetter wrote:
> > On Mon, Apr 01, 2019 at 09:49:30AM -0400, Sean Paul wrote:
> > > On Fri, Mar 29, 2019 at 08:21:31PM +0100, Daniel Vetter wrote:
> > > > On Fri, Mar 29, 2019 at 7:10 PM Sean Paul <sean@poorly.run> wrote:
> > > > >
> > > > > On Fri, Mar 29, 2019 at 04:36:32PM +0100, Daniel Vetter wrote:
> > > > > > On Fri, Mar 29, 2019 at 09:16:59AM -0400, Sean Paul wrote:
> > > > > > > On Fri, Mar 29, 2019 at 09:21:10AM +0100, Daniel Vetter wrote:
> > > > > > > > On Thu, Mar 28, 2019 at 05:03:03PM -0400, Sean Paul wrote:
> > > > > > > > > On Wed, Mar 27, 2019 at 07:15:00PM +0100, Daniel Vetter wrote:
> > > > > > > > > > On Tue, Mar 26, 2019 at 04:44:54PM -0400, Sean Paul wrote:
> > > > > > > > > > > From: Sean Paul <seanpaul@chromium.org>
> > > > > > > > > > >
> > > > > > > > > > > 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' and/or
> > > > > > > > > > > 'self_refresh_changed' state in crtc_state and connector_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)
> > > > > > > > > > >
> > > > > > > > > > > Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
> > > > > > > > > > > Signed-off-by: Sean Paul <seanpaul@chromium.org>
> > > > > > > > > > > ---
> > > > > > > > > > >  Documentation/gpu/drm-kms-helpers.rst     |   9 +
> > > > > > > > > > >  drivers/gpu/drm/Makefile                  |   3 +-
> > > > > > > > > > >  drivers/gpu/drm/drm_atomic.c              |   4 +
> > > > > > > > > > >  drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
> > > > > > > > > > >  drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
> > > > > > > > > > >  drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
> > > > > > > > > > >  drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
> > > > > > > > > > >  include/drm/drm_atomic.h                  |  15 ++
> > > > > > > > > > >  include/drm/drm_connector.h               |  31 ++++
> > > > > > > > > > >  include/drm/drm_crtc.h                    |  19 ++
> > > > > > > > > > >  include/drm/drm_self_refresh_helper.h     |  23 +++
> > > > > > > > > > >  11 files changed, 360 insertions(+), 5 deletions(-)
> > > > > > > > > > >  create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
> > > > > > > > > > >  create mode 100644 include/drm/drm_self_refresh_helper.h
> > > > > > > > > > >
> > > > > > >
> > > > > > > /snip
> > > > > > >
> > > > > > > > > > > index 4985384e51f6..ec90c527deed 100644
> > > > > > > > > > > --- a/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > > > > > > > +++ b/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > > > > > > > @@ -105,6 +105,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);
> > > > > > > > > > >
> > > > > > > > > > > @@ -370,6 +374,10 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> > > > > > > > > > >
> > > > > > > > > > >     /* Don't copy over a writeback job, they are used only once */
> > > > > > > > > > >     state->writeback_job = NULL;
> > > > > > > > > > > +
> > > > > > > > > > > +   /* Self refresh should be canceled when a new update is available */
> > > > > > > > > > > +   state->self_refresh_changed = state->self_refresh_active;
> > > > > > > > > > > +   state->self_refresh_active = false;
> > > > > > > > > >
> > > > > > > > > > Why the duplication in self-refresh tracking? Connectors never have a
> > > > > > > > > > different self-refresh state, and you can always look at the right
> > > > > > > > > > crtc_state. Duplication just gives us the chance to screw up and get out
> > > > > > > > > > of sync (e.g. if the crtc for a connector changes).
> > > > > > > > > >
> > > > > > > > >
> > > > > > > > > On disable the crtc is cleared from connector_state, so we don't have access to
> > > > > > > > > it. If I add the appropriate atomic_enable/disable hooks as suggested below, we
> > > > > > > > > should be able to nuke these.
> > > > > > > >
> > > > > > > > Yeah we'd need the old state to look at the crtc and all that. Which is a
> > > > > > > > lot more trickier.
> > > > > > > >
> > > > > > > > Since it's such a special case, should we have a dedicated callback for
> > > > > > > > the direct self-refresh -> completely off transition? It'll be asymetric,
> > > > > > > > but that's the nature of this I think.
> > > > > > >
> > > > > > > Right, the asymmetry is really annoying here. If the driver is SR-aware, it makes
> > > > > > > sense since SR-active to disable is a real transition. However if the driver is
> > > > > > > not SR-aware (ie: it just gets turned off when SR becomes active), the disable
> > > > > > > function gets called twice without an enable. So that changes the "for every
> > > > > > > enable there is a disable and vice versa" assumption.
> > > > > > >
> > > > > > > This is one of the benefits of the v1 design, SR was bolted on and no existing
> > > > > > > rules (async/no_modeset/enable-disable pairs) were [explicitly] broken. That's
> > > > > > > not to say it was better, it wasn't, but it was a big consideration.
> > > > > > >
> > > > > > > So, what to do.
> > > > > > >
> > > > > > > I really like the idea that drivers shouldn't have to be SR-aware to be involved
> > > > > > > in the pipeline. So if we add a hook for this like you suggest, we could avoid
> > > > > > > calling disable twice on anything not SR-aware. We would need to add the hook on
> > > > > > > crtc/encoder/bridge to make sure you could mix n' match SR-aware and
> > > > > > > non-SR-aware devices.
> > > > > > >
> > > > > > > It probably makes sense to just add matching SR hooks at this point. Since if
> > > > > > > the driver is doing something special in disable, it'll need to do something
> > > > > > > special in enable. It also reserves enable and disable for what they've
> > > > > > > traditionally done. If a device is not SR-aware, it'll just fall back to the
> > > > > > > full enable/disable and we'll make sure to not double up on the disable in the
> > > > > > > helpers.
> > > > > > >
> > > > > > > So we'll keep symmetry, and avoid having an awful hook name like
> > > > > > > disable_from_self_refresh.. yuck!
> > > > > > >
> > > > > > > Thoughts?
> > > > > >
> > > > > > I like the asymetry actually, it has grown on a bit while working out and
> > > > > > pondering this :-)
> > > > > >
> > > > >
> > > > > I'm not quite there with you, I still think it's better to split it all out.
> > > > >
> > > > > > Benefits:
> > > > > > - we keep the 100% symmetry of enable/disable hooks
> > > > > > - self-refresh aware connector code also gets a bit simpler I think: in
> > > > > >   the normal enable/disable hooks it can just check for
> > > > > >   connector->state->crtc->state->self_refresh_active for sr state changes
> > > > > >   while the pipe is logically staying on
> > > > > > - the one asymmetric case due to this design where we disable the pipe
> > > > > >   harder has an awkward special hook, which gives us a great opportunity
> > > > > >   to explain why it's needed
> > > > > > - nothing changes for non-sr aware drivers
> > > > > > - also no need to duplicate sr state into connectors, since it's all
> > > > > >   fairly explit already in all three state transitions.
> > > > >
> > > > > To be fair, only one of these is exclusive to asymmetry, and it's the one that
> > > > > provides the opportunity to add a comment. If the sr functions are symmetric,
> > > > > the code becomes much more "normal" and less deserving of the explanation.
> > > > >
> > > > > The reason I would like to split out entry and exit is that it makes the driver
> > > > > code a bit easier to rationalize. Currently we need to check the state at the
> > > > > beginning of enable/disable to determine whether we want the full enable/disable
> > > > > or the psr exit/enter. So the sr_disable function would really just be plain
> > > > > old disable without the special casing at the top. In that case, we don't even
> > > > > need the separate function, we could just limit disable calls only on those
> > > > > objects which are effectively on (active || sr). That starts sounding a lot like
> > > > > what we already have here.
> > > > >
> > > > > Further, doing SR in enable/disable is really just legacy from v1 which tried to
> > > > > keep as much the same as possible. Now that we're "in it", I think it makes
> > > > > sense to go all in and make SR a first class citizen.
> > > >
> > > > Hm, question is: How many hooks do you need? Just something on the
> > > > connector, or on the encoder, or everywhere?
> > >
> > > bridge/encoder/crtc all do special things during SR transitions, I don't think
> > > connector is necessary. This is the same for any .sr_disable function, everyone
> > > would need to implement it.
> >
> > Hm, that's a lot of new callbacks ...
> >
> > > > And how do you handle the
> > > > various state transitions. On the disable side we have:
> > > > - active on -> active off, no sr (userspace disables crtc)
> > > > - active on, sr off -> active ooff, sr on (sr timer fires and suspends crtc)
> > > > - active off, sr on -> active off, sr off (userspace disable crtc
> > > > while crtc is in sr)
> > > > These are all "logical active on" -> "something" transitions where we
> > > > disable something (crtc, or display or both)
> > > >
> > > > So in a way you'd need 3 hooks here for the full matrix.
> > > > And they all
> > > > kinda disable something. On the enable side we have:
> > > > - active off, sr off -> active on, sr off (userspace enables crtc)
> > > > - active off, sr on -> active on, sr off (userspace does a pageflip, stops sr)
> > > > Here we either enable the crtc (display already on) or both. Since we
> > > > only go into sr with the timer there's no 3rd case of only enabling
> > > > the display. So still asymetric, even with lots more hooks.
> > >
> > > We don't need the (active off, sr on) -> (active off, sr off) (third) case
> > > above, it's the same as the first. Just doing a full disable is sufficient,
> > > so you would have symmetry in the enable/disable calls and asymmetry in the
> > > sr calls. This is similar to enabling a plane, or turning other HW features on
> > > while enabled. SR is after all just a feature of the hardware.
> >
> > Hm yeah I guess we can treat it like plane disabling, which implicitly
> > happens in crtc->disable too. Or the implicit plane enable in crtc->enable
> > (although that case doesn't exist for sr, since we never go directly into
> > sr).
> >
> > > > If you want the full matrix, there's going to be a _lot_ of hooks. I
> > > > think slightly more awkward driver, but less hooks is better. Hence
> > > > the slightly awkward middle ground of a special disable_from_sr hook.
> > > > But maybe there's a better option somewhere else ...
> > >
> > > There's really no reason to even have the sr_disable function. The .disable
> > > function in the driver will already need special casing to detect psr_entry
> > > vs full disable, so it'd be better to just call disable twice. The .sr_disable
> > > function would always just do a full disable (ie: the .disable implementation
> > > without the sr checks at the top).
> > >
> > > So the debate should be: add sr_enable/disable pair of hooks, or overload
> > > disable with asymmetry (current implementation).
> >
> > I guess that means we're back to no new hooks, and the driver just dtrt
> > in the existing hooks with the state transition bits we have? I thought
> > the issue with that is that we can't get at all the right bits, hence the
> > sr_disable special case hook.
> >
> > Or is your plan to roll out a full new set of hooks, equipped with
> > old/new_state for everything? I think we'd only need old/new_state for the
> > object at hand, since with the old_state you can get at drm_atomic_state,
> > which allows you to get anything else really.
>
> I don't think we even need to pass the state to the sr hooks, just add
>
> void self_refresh_enter(struct drm_<type> *<name>);
> void self_refresh_exit(struct drm_<type> *<name>);
>
> to the funcs vtable for crtc/encoder/bridge.
>
> Of course it's not _quite_ as straightforward as that :)
>
> With the current model, the powerdown/powerup order of components is implicitly
> broken. With this new model, it's much more obvious, this is easiest to
> illustrate with bridges, but it's true for crtcs and encoders as well.
>
> Assume you have the following bridge chain:
>
> ENC0 (not SR-aware)
>         -> BR0 (SR-aware)
>                 -> BR1 (not SR-aware)
>                         -> BR2 (SR-aware)
>                                 -> CON0
>
> An SR-enter transition would be:
>         BR2->self_refresh_enter
>         BR1->disable
>         BR0->self_refresh_enter
>         ENC0->disable
>         BR1->post_disable
>
> SR-exit is:
>         BR1->pre_enable
>         ENC0->enable
>         BR0->self_refresh_exit
>         BR1->enable
>         BR2->self_refresh_exit
>
> Disabling from SR becomes:
>         BR2->disable
>         BR0->disable
>         BR2->post_disable
>         BR0->post_disable
>
> So I'm starting to question falling back on disable. I think it was a fine
> choice when we would exit psr before disable (ie: v1), but I think it might be
> too complicated now. We could make BR2 and BR0 do the right thing on
> disable-from-SR, but I'm worried that mixing up the order for SR-unaware devices
> (ENC0/BR1) might cause issues.
>
> Perhaps we should scale this back and just treat self_refresh as its own thing
> and not go through the enable/disable path at all. Devices which are not
> SR-aware stay on (which has it's own issues if BR1 underflows because it's
> expecting video from BR0). Maybe we have to ensure the entire pipe is SR-aware
> before we do an SR-enter.

Imo if the driver tries to enable SR on a pipe where some pieces
aren't SR aware, that's a driver bug. And if you really want to
implement the above sequence (well, need to implement it), then I
agree that helpers aren't the thing you're looking for and you should
just roll your own modeset code.

But we started all this assuming that you're hw isn't in the need of
the full state matrix with hooks for everything, hence that maybe a
helper would make sense. It feels a bit like the discussion lost
contact with the (driver) reality ...

> Thoughts?

... so imo if we can help out drivers by repurposing the existing
hooks to cover most cases, with a few special cases in callbacks, then
we can roll these helpers. If that doesn't happen, then probably
better if each driver just rolls their own sr enter/exit code and
calls it done. It's not like we don't allow subclassing of states or
also vtable hooks where you could just add more of your own stuff as
you see fit. But I thought sr for most devices would amount to a)
shutting the pipe down b) some special casing to keep the display
alive and nothing else. But now it sounds like you need hooks for
everything, which probably doesnt make sense to cover in the helpers.
-Daniel

>
> Sean
>
> >
> > Or should we just add drm_atomic_state *state to all these hooks? That'd
> > probably the most flexible long-term thing. Could even be done with cocci,
> > so we don't need new atomic_disable2 calls and silly things like that.
> > -Daniel
> > >
> > > Sean
> > >
> > > > -Daniel
> > > >
> > > > >
> > > > > Sean
> > > > >
> > > > > >
> > > > > > - SR on can only happen if the logical crtc_state->active is on and stays on
> > > > > > - SR can get disabled in 2 subcases
> > > > > >   - logical active state stays on -> handled with existing hooks
> > > > > >   - logical active state also goes off -> existing hooks all skip (because
> > > > > >     active=false -> active=false is a no-op), the special ->sr_disable
> > > > > >     takes care
> > > > > >
> > > > > > It feels like this is clean, integrates well with atomic helpers overall
> > > > > > and it even makes sense. At least to my slightly oxygen deprived mind
> > > > > > right now ...
> > > > > >
> > > > > > > > > > >  }
> > > > > > > > > > >  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
> > > > > > > > > > >
> > > > > > >
> > > > > > > /snip
> > > > > > >
> > > > > > > > > > > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> > > > > > > > > > > index c8061992d6cb..0ae7e812ec62 100644
> > > > > > > > > > > --- a/include/drm/drm_connector.h
> > > > > > > > > > > +++ b/include/drm/drm_connector.h
> > > > > > > > > > > @@ -501,6 +501,37 @@ struct drm_connector_state {
> > > > > > > > > > >     /** @tv: TV connector state */
> > > > > > > > > > >     struct drm_tv_connector_state tv;
> > > > > > > > > > >
> > > > > > > > > > > +   /**
> > > > > > > > > > > +    * @self_refresh_changed:
> > > > > > > > > > > +    *
> > > > > > > > > > > +    * Set true when self refresh status has changed. This is useful for
> > > > > > > > > > > +    * use in encoder/bridge enable where the old state is unavailable to
> > > > > > > > > > > +    * the driver and it needs to know whether the enable transition is a
> > > > > > > > > > > +    * full transition, or if it just needs to exit self refresh mode.
> > > > > > > > > >
> > > > > > > > > > Uh, we just need proper atomic callbacks with all the states available.
> > > > > > > > > > Once you have one, you can get at the others.
> > > > > > > > > >
> > > > > > > > >
> > > > > > > > > Well, sure, we could do that too :)
> > > > > > > >
> > > > > > > > tbh I'm not sure whether that's really better, the duplication just irks
> > > > > > > > me. With a new callback for the special self-refresh disable (I guess we
> > > > > > > > only need that on the connector), plus looking at
> > > > > > > > connector->state->crtc->state->self_refresh, I think we'd be covered
> > > > > > > > as-is? Or is there a corner case I'm still missing?
> > > > > > > >
> > > > > > >
> > > > > > > I think we can remove self_refresh_changed/self_refresh_active if we implement
> > > > > > > dedicated hooks for self_refresh_enter/exit. We'll want to keep
> > > > > > > self_refresh_aware around since the presence of the callback implementations
> > > > > > > does not imply the panel connected supports SR.
> > > > > >
> > > > > > Yup, self_refresh_aware is needed.
> > > > > >
> > > > > > > As mentioned above, we'll need these hooks on everything in the pipeline to be
> > > > > > > fully covered.
> > > > > >
> > > > > > Let's just do the ->sr_disable hook for now. I don't think we need all the
> > > > > > others really.
> > > > > >
> > > > > > Cheers, Daniel
> > > > > > --
> > > > > > Daniel Vetter
> > > > > > Software Engineer, Intel Corporation
> > > > > > http://blog.ffwll.ch
> > > > >
> > > > > --
> > > > > Sean Paul, Software Engineer, Google / Chromium OS
> > > >
> > > >
> > > >
> > > > --
> > > > Daniel Vetter
> > > > Software Engineer, Intel Corporation
> > > > +41 (0) 79 365 57 48 - http://blog.ffwll.ch
> > >
> > > --
> > > Sean Paul, Software Engineer, Google / Chromium OS
> >
> > --
> > Daniel Vetter
> > Software Engineer, Intel Corporation
> > http://blog.ffwll.ch
>
> --
> Sean Paul, Software Engineer, Google / Chromium OS
Sean Paul April 2, 2019, 4:47 p.m. UTC | #19
On Tue, Apr 02, 2019 at 06:05:48PM +0200, Daniel Vetter wrote:
> On Tue, Apr 2, 2019 at 3:24 PM Sean Paul <sean@poorly.run> wrote:
> >
> > On Tue, Apr 02, 2019 at 09:49:00AM +0200, Daniel Vetter wrote:
> > > On Mon, Apr 01, 2019 at 09:49:30AM -0400, Sean Paul wrote:
> > > > On Fri, Mar 29, 2019 at 08:21:31PM +0100, Daniel Vetter wrote:
> > > > > On Fri, Mar 29, 2019 at 7:10 PM Sean Paul <sean@poorly.run> wrote:
> > > > > >
> > > > > > On Fri, Mar 29, 2019 at 04:36:32PM +0100, Daniel Vetter wrote:
> > > > > > > On Fri, Mar 29, 2019 at 09:16:59AM -0400, Sean Paul wrote:
> > > > > > > > On Fri, Mar 29, 2019 at 09:21:10AM +0100, Daniel Vetter wrote:
> > > > > > > > > On Thu, Mar 28, 2019 at 05:03:03PM -0400, Sean Paul wrote:
> > > > > > > > > > On Wed, Mar 27, 2019 at 07:15:00PM +0100, Daniel Vetter wrote:
> > > > > > > > > > > On Tue, Mar 26, 2019 at 04:44:54PM -0400, Sean Paul wrote:
> > > > > > > > > > > > From: Sean Paul <seanpaul@chromium.org>
> > > > > > > > > > > >
> > > > > > > > > > > > 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' and/or
> > > > > > > > > > > > 'self_refresh_changed' state in crtc_state and connector_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)
> > > > > > > > > > > >
> > > > > > > > > > > > Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
> > > > > > > > > > > > Signed-off-by: Sean Paul <seanpaul@chromium.org>
> > > > > > > > > > > > ---
> > > > > > > > > > > >  Documentation/gpu/drm-kms-helpers.rst     |   9 +
> > > > > > > > > > > >  drivers/gpu/drm/Makefile                  |   3 +-
> > > > > > > > > > > >  drivers/gpu/drm/drm_atomic.c              |   4 +
> > > > > > > > > > > >  drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
> > > > > > > > > > > >  drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
> > > > > > > > > > > >  drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
> > > > > > > > > > > >  drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
> > > > > > > > > > > >  include/drm/drm_atomic.h                  |  15 ++
> > > > > > > > > > > >  include/drm/drm_connector.h               |  31 ++++
> > > > > > > > > > > >  include/drm/drm_crtc.h                    |  19 ++
> > > > > > > > > > > >  include/drm/drm_self_refresh_helper.h     |  23 +++
> > > > > > > > > > > >  11 files changed, 360 insertions(+), 5 deletions(-)
> > > > > > > > > > > >  create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
> > > > > > > > > > > >  create mode 100644 include/drm/drm_self_refresh_helper.h
> > > > > > > > > > > >
> > > > > > > >
> > > > > > > > /snip
> > > > > > > >
> > > > > > > > > > > > index 4985384e51f6..ec90c527deed 100644
> > > > > > > > > > > > --- a/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > > > > > > > > +++ b/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > > > > > > > > @@ -105,6 +105,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);
> > > > > > > > > > > >
> > > > > > > > > > > > @@ -370,6 +374,10 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> > > > > > > > > > > >
> > > > > > > > > > > >     /* Don't copy over a writeback job, they are used only once */
> > > > > > > > > > > >     state->writeback_job = NULL;
> > > > > > > > > > > > +
> > > > > > > > > > > > +   /* Self refresh should be canceled when a new update is available */
> > > > > > > > > > > > +   state->self_refresh_changed = state->self_refresh_active;
> > > > > > > > > > > > +   state->self_refresh_active = false;
> > > > > > > > > > >
> > > > > > > > > > > Why the duplication in self-refresh tracking? Connectors never have a
> > > > > > > > > > > different self-refresh state, and you can always look at the right
> > > > > > > > > > > crtc_state. Duplication just gives us the chance to screw up and get out
> > > > > > > > > > > of sync (e.g. if the crtc for a connector changes).
> > > > > > > > > > >
> > > > > > > > > >
> > > > > > > > > > On disable the crtc is cleared from connector_state, so we don't have access to
> > > > > > > > > > it. If I add the appropriate atomic_enable/disable hooks as suggested below, we
> > > > > > > > > > should be able to nuke these.
> > > > > > > > >
> > > > > > > > > Yeah we'd need the old state to look at the crtc and all that. Which is a
> > > > > > > > > lot more trickier.
> > > > > > > > >
> > > > > > > > > Since it's such a special case, should we have a dedicated callback for
> > > > > > > > > the direct self-refresh -> completely off transition? It'll be asymetric,
> > > > > > > > > but that's the nature of this I think.
> > > > > > > >
> > > > > > > > Right, the asymmetry is really annoying here. If the driver is SR-aware, it makes
> > > > > > > > sense since SR-active to disable is a real transition. However if the driver is
> > > > > > > > not SR-aware (ie: it just gets turned off when SR becomes active), the disable
> > > > > > > > function gets called twice without an enable. So that changes the "for every
> > > > > > > > enable there is a disable and vice versa" assumption.
> > > > > > > >
> > > > > > > > This is one of the benefits of the v1 design, SR was bolted on and no existing
> > > > > > > > rules (async/no_modeset/enable-disable pairs) were [explicitly] broken. That's
> > > > > > > > not to say it was better, it wasn't, but it was a big consideration.
> > > > > > > >
> > > > > > > > So, what to do.
> > > > > > > >
> > > > > > > > I really like the idea that drivers shouldn't have to be SR-aware to be involved
> > > > > > > > in the pipeline. So if we add a hook for this like you suggest, we could avoid
> > > > > > > > calling disable twice on anything not SR-aware. We would need to add the hook on
> > > > > > > > crtc/encoder/bridge to make sure you could mix n' match SR-aware and
> > > > > > > > non-SR-aware devices.
> > > > > > > >
> > > > > > > > It probably makes sense to just add matching SR hooks at this point. Since if
> > > > > > > > the driver is doing something special in disable, it'll need to do something
> > > > > > > > special in enable. It also reserves enable and disable for what they've
> > > > > > > > traditionally done. If a device is not SR-aware, it'll just fall back to the
> > > > > > > > full enable/disable and we'll make sure to not double up on the disable in the
> > > > > > > > helpers.
> > > > > > > >
> > > > > > > > So we'll keep symmetry, and avoid having an awful hook name like
> > > > > > > > disable_from_self_refresh.. yuck!
> > > > > > > >
> > > > > > > > Thoughts?
> > > > > > >
> > > > > > > I like the asymetry actually, it has grown on a bit while working out and
> > > > > > > pondering this :-)
> > > > > > >
> > > > > >
> > > > > > I'm not quite there with you, I still think it's better to split it all out.
> > > > > >
> > > > > > > Benefits:
> > > > > > > - we keep the 100% symmetry of enable/disable hooks
> > > > > > > - self-refresh aware connector code also gets a bit simpler I think: in
> > > > > > >   the normal enable/disable hooks it can just check for
> > > > > > >   connector->state->crtc->state->self_refresh_active for sr state changes
> > > > > > >   while the pipe is logically staying on
> > > > > > > - the one asymmetric case due to this design where we disable the pipe
> > > > > > >   harder has an awkward special hook, which gives us a great opportunity
> > > > > > >   to explain why it's needed
> > > > > > > - nothing changes for non-sr aware drivers
> > > > > > > - also no need to duplicate sr state into connectors, since it's all
> > > > > > >   fairly explit already in all three state transitions.
> > > > > >
> > > > > > To be fair, only one of these is exclusive to asymmetry, and it's the one that
> > > > > > provides the opportunity to add a comment. If the sr functions are symmetric,
> > > > > > the code becomes much more "normal" and less deserving of the explanation.
> > > > > >
> > > > > > The reason I would like to split out entry and exit is that it makes the driver
> > > > > > code a bit easier to rationalize. Currently we need to check the state at the
> > > > > > beginning of enable/disable to determine whether we want the full enable/disable
> > > > > > or the psr exit/enter. So the sr_disable function would really just be plain
> > > > > > old disable without the special casing at the top. In that case, we don't even
> > > > > > need the separate function, we could just limit disable calls only on those
> > > > > > objects which are effectively on (active || sr). That starts sounding a lot like
> > > > > > what we already have here.
> > > > > >
> > > > > > Further, doing SR in enable/disable is really just legacy from v1 which tried to
> > > > > > keep as much the same as possible. Now that we're "in it", I think it makes
> > > > > > sense to go all in and make SR a first class citizen.
> > > > >
> > > > > Hm, question is: How many hooks do you need? Just something on the
> > > > > connector, or on the encoder, or everywhere?
> > > >
> > > > bridge/encoder/crtc all do special things during SR transitions, I don't think
> > > > connector is necessary. This is the same for any .sr_disable function, everyone
> > > > would need to implement it.
> > >
> > > Hm, that's a lot of new callbacks ...
> > >
> > > > > And how do you handle the
> > > > > various state transitions. On the disable side we have:
> > > > > - active on -> active off, no sr (userspace disables crtc)
> > > > > - active on, sr off -> active ooff, sr on (sr timer fires and suspends crtc)
> > > > > - active off, sr on -> active off, sr off (userspace disable crtc
> > > > > while crtc is in sr)
> > > > > These are all "logical active on" -> "something" transitions where we
> > > > > disable something (crtc, or display or both)
> > > > >
> > > > > So in a way you'd need 3 hooks here for the full matrix.
> > > > > And they all
> > > > > kinda disable something. On the enable side we have:
> > > > > - active off, sr off -> active on, sr off (userspace enables crtc)
> > > > > - active off, sr on -> active on, sr off (userspace does a pageflip, stops sr)
> > > > > Here we either enable the crtc (display already on) or both. Since we
> > > > > only go into sr with the timer there's no 3rd case of only enabling
> > > > > the display. So still asymetric, even with lots more hooks.
> > > >
> > > > We don't need the (active off, sr on) -> (active off, sr off) (third) case
> > > > above, it's the same as the first. Just doing a full disable is sufficient,
> > > > so you would have symmetry in the enable/disable calls and asymmetry in the
> > > > sr calls. This is similar to enabling a plane, or turning other HW features on
> > > > while enabled. SR is after all just a feature of the hardware.
> > >
> > > Hm yeah I guess we can treat it like plane disabling, which implicitly
> > > happens in crtc->disable too. Or the implicit plane enable in crtc->enable
> > > (although that case doesn't exist for sr, since we never go directly into
> > > sr).
> > >
> > > > > If you want the full matrix, there's going to be a _lot_ of hooks. I
> > > > > think slightly more awkward driver, but less hooks is better. Hence
> > > > > the slightly awkward middle ground of a special disable_from_sr hook.
> > > > > But maybe there's a better option somewhere else ...
> > > >
> > > > There's really no reason to even have the sr_disable function. The .disable
> > > > function in the driver will already need special casing to detect psr_entry
> > > > vs full disable, so it'd be better to just call disable twice. The .sr_disable
> > > > function would always just do a full disable (ie: the .disable implementation
> > > > without the sr checks at the top).
> > > >
> > > > So the debate should be: add sr_enable/disable pair of hooks, or overload
> > > > disable with asymmetry (current implementation).
> > >
> > > I guess that means we're back to no new hooks, and the driver just dtrt
> > > in the existing hooks with the state transition bits we have? I thought
> > > the issue with that is that we can't get at all the right bits, hence the
> > > sr_disable special case hook.
> > >
> > > Or is your plan to roll out a full new set of hooks, equipped with
> > > old/new_state for everything? I think we'd only need old/new_state for the
> > > object at hand, since with the old_state you can get at drm_atomic_state,
> > > which allows you to get anything else really.
> >
> > I don't think we even need to pass the state to the sr hooks, just add
> >
> > void self_refresh_enter(struct drm_<type> *<name>);
> > void self_refresh_exit(struct drm_<type> *<name>);
> >
> > to the funcs vtable for crtc/encoder/bridge.
> >
> > Of course it's not _quite_ as straightforward as that :)
> >
> > With the current model, the powerdown/powerup order of components is implicitly
> > broken. With this new model, it's much more obvious, this is easiest to
> > illustrate with bridges, but it's true for crtcs and encoders as well.
> >
> > Assume you have the following bridge chain:
> >
> > ENC0 (not SR-aware)
> >         -> BR0 (SR-aware)
> >                 -> BR1 (not SR-aware)
> >                         -> BR2 (SR-aware)
> >                                 -> CON0
> >
> > An SR-enter transition would be:
> >         BR2->self_refresh_enter
> >         BR1->disable
> >         BR0->self_refresh_enter
> >         ENC0->disable
> >         BR1->post_disable
> >
> > SR-exit is:
> >         BR1->pre_enable
> >         ENC0->enable
> >         BR0->self_refresh_exit
> >         BR1->enable
> >         BR2->self_refresh_exit
> >
> > Disabling from SR becomes:
> >         BR2->disable
> >         BR0->disable
> >         BR2->post_disable
> >         BR0->post_disable
> >
> > So I'm starting to question falling back on disable. I think it was a fine
> > choice when we would exit psr before disable (ie: v1), but I think it might be
> > too complicated now. We could make BR2 and BR0 do the right thing on
> > disable-from-SR, but I'm worried that mixing up the order for SR-unaware devices
> > (ENC0/BR1) might cause issues.
> >
> > Perhaps we should scale this back and just treat self_refresh as its own thing
> > and not go through the enable/disable path at all. Devices which are not
> > SR-aware stay on (which has it's own issues if BR1 underflows because it's
> > expecting video from BR0). Maybe we have to ensure the entire pipe is SR-aware
> > before we do an SR-enter.
> 
> Imo if the driver tries to enable SR on a pipe where some pieces
> aren't SR aware, that's a driver bug.

If you assume that most bridges (aside from the "bridges" that represent shared
silicon IP) can be arbitrarily mixed with most other bridges and drivers, then
yeah, this is unavoidable and not something that's easily fixed since we'd need
to make all SR-aware or at the very least audit them to make sure they don't
foul up if the things around them go to sleep.

> And if you really want to
> implement the above sequence (well, need to implement it), then I
> agree that helpers aren't the thing you're looking for and you should
> just roll your own modeset code.
> 
> But we started all this assuming that you're hw isn't in the need of
> the full state matrix with hooks for everything, hence that maybe a
> helper would make sense. It feels a bit like the discussion lost
> contact with the (driver) reality ...
> 
> > Thoughts?
> 
> ... so imo if we can help out drivers by repurposing the existing
> hooks to cover most cases, with a few special cases in callbacks, then
> we can roll these helpers. If that doesn't happen, then probably
> better if each driver just rolls their own sr enter/exit code and
> calls it done. It's not like we don't allow subclassing of states or
> also vtable hooks where you could just add more of your own stuff as
> you see fit. But I thought sr for most devices would amount to a)
> shutting the pipe down b) some special casing to keep the display
> alive and nothing else. But now it sounds like you need hooks for
> everything, which probably doesnt make sense to cover in the helpers.

For most everything upstream of the connector, you don't _need_ a hook since
shutting them down is fine. However if your crtc takes a while to come back on
(like with rockchip), then you start to want hooks everywhere to optimize
things.

I'll put this on the shelf and wait for a few more drivers to implement their
own SR. Perhaps a pattern will emerge.

Sean


> -Daniel
> 
> >
> > Sean
> >
> > >
> > > Or should we just add drm_atomic_state *state to all these hooks? That'd
> > > probably the most flexible long-term thing. Could even be done with cocci,
> > > so we don't need new atomic_disable2 calls and silly things like that.
> > > -Daniel
> > > >
> > > > Sean
> > > >
> > > > > -Daniel
> > > > >
> > > > > >
> > > > > > Sean
> > > > > >
> > > > > > >
> > > > > > > - SR on can only happen if the logical crtc_state->active is on and stays on
> > > > > > > - SR can get disabled in 2 subcases
> > > > > > >   - logical active state stays on -> handled with existing hooks
> > > > > > >   - logical active state also goes off -> existing hooks all skip (because
> > > > > > >     active=false -> active=false is a no-op), the special ->sr_disable
> > > > > > >     takes care
> > > > > > >
> > > > > > > It feels like this is clean, integrates well with atomic helpers overall
> > > > > > > and it even makes sense. At least to my slightly oxygen deprived mind
> > > > > > > right now ...
> > > > > > >
> > > > > > > > > > > >  }
> > > > > > > > > > > >  EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
> > > > > > > > > > > >
> > > > > > > >
> > > > > > > > /snip
> > > > > > > >
> > > > > > > > > > > > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
> > > > > > > > > > > > index c8061992d6cb..0ae7e812ec62 100644
> > > > > > > > > > > > --- a/include/drm/drm_connector.h
> > > > > > > > > > > > +++ b/include/drm/drm_connector.h
> > > > > > > > > > > > @@ -501,6 +501,37 @@ struct drm_connector_state {
> > > > > > > > > > > >     /** @tv: TV connector state */
> > > > > > > > > > > >     struct drm_tv_connector_state tv;
> > > > > > > > > > > >
> > > > > > > > > > > > +   /**
> > > > > > > > > > > > +    * @self_refresh_changed:
> > > > > > > > > > > > +    *
> > > > > > > > > > > > +    * Set true when self refresh status has changed. This is useful for
> > > > > > > > > > > > +    * use in encoder/bridge enable where the old state is unavailable to
> > > > > > > > > > > > +    * the driver and it needs to know whether the enable transition is a
> > > > > > > > > > > > +    * full transition, or if it just needs to exit self refresh mode.
> > > > > > > > > > >
> > > > > > > > > > > Uh, we just need proper atomic callbacks with all the states available.
> > > > > > > > > > > Once you have one, you can get at the others.
> > > > > > > > > > >
> > > > > > > > > >
> > > > > > > > > > Well, sure, we could do that too :)
> > > > > > > > >
> > > > > > > > > tbh I'm not sure whether that's really better, the duplication just irks
> > > > > > > > > me. With a new callback for the special self-refresh disable (I guess we
> > > > > > > > > only need that on the connector), plus looking at
> > > > > > > > > connector->state->crtc->state->self_refresh, I think we'd be covered
> > > > > > > > > as-is? Or is there a corner case I'm still missing?
> > > > > > > > >
> > > > > > > >
> > > > > > > > I think we can remove self_refresh_changed/self_refresh_active if we implement
> > > > > > > > dedicated hooks for self_refresh_enter/exit. We'll want to keep
> > > > > > > > self_refresh_aware around since the presence of the callback implementations
> > > > > > > > does not imply the panel connected supports SR.
> > > > > > >
> > > > > > > Yup, self_refresh_aware is needed.
> > > > > > >
> > > > > > > > As mentioned above, we'll need these hooks on everything in the pipeline to be
> > > > > > > > fully covered.
> > > > > > >
> > > > > > > Let's just do the ->sr_disable hook for now. I don't think we need all the
> > > > > > > others really.
> > > > > > >
> > > > > > > Cheers, Daniel
> > > > > > > --
> > > > > > > Daniel Vetter
> > > > > > > Software Engineer, Intel Corporation
> > > > > > > http://blog.ffwll.ch
> > > > > >
> > > > > > --
> > > > > > Sean Paul, Software Engineer, Google / Chromium OS
> > > > >
> > > > >
> > > > >
> > > > > --
> > > > > Daniel Vetter
> > > > > Software Engineer, Intel Corporation
> > > > > +41 (0) 79 365 57 48 - http://blog.ffwll.ch
> > > >
> > > > --
> > > > Sean Paul, Software Engineer, Google / Chromium OS
> > >
> > > --
> > > Daniel Vetter
> > > Software Engineer, Intel Corporation
> > > http://blog.ffwll.ch
> >
> > --
> > Sean Paul, Software Engineer, Google / Chromium OS
> 
> 
> 
> -- 
> Daniel Vetter
> Software Engineer, Intel Corporation
> +41 (0) 79 365 57 48 - http://blog.ffwll.ch
Daniel Vetter April 3, 2019, 6:52 a.m. UTC | #20
On Tue, Apr 2, 2019 at 6:47 PM Sean Paul <sean@poorly.run> wrote:
> On Tue, Apr 02, 2019 at 06:05:48PM +0200, Daniel Vetter wrote:
> > On Tue, Apr 2, 2019 at 3:24 PM Sean Paul <sean@poorly.run> wrote:
> > >
> > > On Tue, Apr 02, 2019 at 09:49:00AM +0200, Daniel Vetter wrote:
> > > > On Mon, Apr 01, 2019 at 09:49:30AM -0400, Sean Paul wrote:
> > > > > On Fri, Mar 29, 2019 at 08:21:31PM +0100, Daniel Vetter wrote:
> > > > > > On Fri, Mar 29, 2019 at 7:10 PM Sean Paul <sean@poorly.run> wrote:
> > > > > > >
> > > > > > > On Fri, Mar 29, 2019 at 04:36:32PM +0100, Daniel Vetter wrote:
> > > > > > > > On Fri, Mar 29, 2019 at 09:16:59AM -0400, Sean Paul wrote:
> > > > > > > > > On Fri, Mar 29, 2019 at 09:21:10AM +0100, Daniel Vetter wrote:
> > > > > > > > > > On Thu, Mar 28, 2019 at 05:03:03PM -0400, Sean Paul wrote:
> > > > > > > > > > > On Wed, Mar 27, 2019 at 07:15:00PM +0100, Daniel Vetter wrote:
> > > > > > > > > > > > On Tue, Mar 26, 2019 at 04:44:54PM -0400, Sean Paul wrote:
> > > > > > > > > > > > > From: Sean Paul <seanpaul@chromium.org>
> > > > > > > > > > > > >
> > > > > > > > > > > > > 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' and/or
> > > > > > > > > > > > > 'self_refresh_changed' state in crtc_state and connector_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)
> > > > > > > > > > > > >
> > > > > > > > > > > > > Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-2-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>
> > > > > > > > > > > > > Signed-off-by: Sean Paul <seanpaul@chromium.org>
> > > > > > > > > > > > > ---
> > > > > > > > > > > > >  Documentation/gpu/drm-kms-helpers.rst     |   9 +
> > > > > > > > > > > > >  drivers/gpu/drm/Makefile                  |   3 +-
> > > > > > > > > > > > >  drivers/gpu/drm/drm_atomic.c              |   4 +
> > > > > > > > > > > > >  drivers/gpu/drm/drm_atomic_helper.c       |  36 +++-
> > > > > > > > > > > > >  drivers/gpu/drm/drm_atomic_state_helper.c |   8 +
> > > > > > > > > > > > >  drivers/gpu/drm/drm_atomic_uapi.c         |   5 +-
> > > > > > > > > > > > >  drivers/gpu/drm/drm_self_refresh_helper.c | 212 ++++++++++++++++++++++
> > > > > > > > > > > > >  include/drm/drm_atomic.h                  |  15 ++
> > > > > > > > > > > > >  include/drm/drm_connector.h               |  31 ++++
> > > > > > > > > > > > >  include/drm/drm_crtc.h                    |  19 ++
> > > > > > > > > > > > >  include/drm/drm_self_refresh_helper.h     |  23 +++
> > > > > > > > > > > > >  11 files changed, 360 insertions(+), 5 deletions(-)
> > > > > > > > > > > > >  create mode 100644 drivers/gpu/drm/drm_self_refresh_helper.c
> > > > > > > > > > > > >  create mode 100644 include/drm/drm_self_refresh_helper.h
> > > > > > > > > > > > >
> > > > > > > > >
> > > > > > > > > /snip
> > > > > > > > >
> > > > > > > > > > > > > index 4985384e51f6..ec90c527deed 100644
> > > > > > > > > > > > > --- a/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > > > > > > > > > +++ b/drivers/gpu/drm/drm_atomic_state_helper.c
> > > > > > > > > > > > > @@ -105,6 +105,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);
> > > > > > > > > > > > >
> > > > > > > > > > > > > @@ -370,6 +374,10 @@ __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
> > > > > > > > > > > > >
> > > > > > > > > > > > >     /* Don't copy over a writeback job, they are used only once */
> > > > > > > > > > > > >     state->writeback_job = NULL;
> > > > > > > > > > > > > +
> > > > > > > > > > > > > +   /* Self refresh should be canceled when a new update is available */
> > > > > > > > > > > > > +   state->self_refresh_changed = state->self_refresh_active;
> > > > > > > > > > > > > +   state->self_refresh_active = false;
> > > > > > > > > > > >
> > > > > > > > > > > > Why the duplication in self-refresh tracking? Connectors never have a
> > > > > > > > > > > > different self-refresh state, and you can always look at the right
> > > > > > > > > > > > crtc_state. Duplication just gives us the chance to screw up and get out
> > > > > > > > > > > > of sync (e.g. if the crtc for a connector changes).
> > > > > > > > > > > >
> > > > > > > > > > >
> > > > > > > > > > > On disable the crtc is cleared from connector_state, so we don't have access to
> > > > > > > > > > > it. If I add the appropriate atomic_enable/disable hooks as suggested below, we
> > > > > > > > > > > should be able to nuke these.
> > > > > > > > > >
> > > > > > > > > > Yeah we'd need the old state to look at the crtc and all that. Which is a
> > > > > > > > > > lot more trickier.
> > > > > > > > > >
> > > > > > > > > > Since it's such a special case, should we have a dedicated callback for
> > > > > > > > > > the direct self-refresh -> completely off transition? It'll be asymetric,
> > > > > > > > > > but that's the nature of this I think.
> > > > > > > > >
> > > > > > > > > Right, the asymmetry is really annoying here. If the driver is SR-aware, it makes
> > > > > > > > > sense since SR-active to disable is a real transition. However if the driver is
> > > > > > > > > not SR-aware (ie: it just gets turned off when SR becomes active), the disable
> > > > > > > > > function gets called twice without an enable. So that changes the "for every
> > > > > > > > > enable there is a disable and vice versa" assumption.
> > > > > > > > >
> > > > > > > > > This is one of the benefits of the v1 design, SR was bolted on and no existing
> > > > > > > > > rules (async/no_modeset/enable-disable pairs) were [explicitly] broken. That's
> > > > > > > > > not to say it was better, it wasn't, but it was a big consideration.
> > > > > > > > >
> > > > > > > > > So, what to do.
> > > > > > > > >
> > > > > > > > > I really like the idea that drivers shouldn't have to be SR-aware to be involved
> > > > > > > > > in the pipeline. So if we add a hook for this like you suggest, we could avoid
> > > > > > > > > calling disable twice on anything not SR-aware. We would need to add the hook on
> > > > > > > > > crtc/encoder/bridge to make sure you could mix n' match SR-aware and
> > > > > > > > > non-SR-aware devices.
> > > > > > > > >
> > > > > > > > > It probably makes sense to just add matching SR hooks at this point. Since if
> > > > > > > > > the driver is doing something special in disable, it'll need to do something
> > > > > > > > > special in enable. It also reserves enable and disable for what they've
> > > > > > > > > traditionally done. If a device is not SR-aware, it'll just fall back to the
> > > > > > > > > full enable/disable and we'll make sure to not double up on the disable in the
> > > > > > > > > helpers.
> > > > > > > > >
> > > > > > > > > So we'll keep symmetry, and avoid having an awful hook name like
> > > > > > > > > disable_from_self_refresh.. yuck!
> > > > > > > > >
> > > > > > > > > Thoughts?
> > > > > > > >
> > > > > > > > I like the asymetry actually, it has grown on a bit while working out and
> > > > > > > > pondering this :-)
> > > > > > > >
> > > > > > >
> > > > > > > I'm not quite there with you, I still think it's better to split it all out.
> > > > > > >
> > > > > > > > Benefits:
> > > > > > > > - we keep the 100% symmetry of enable/disable hooks
> > > > > > > > - self-refresh aware connector code also gets a bit simpler I think: in
> > > > > > > >   the normal enable/disable hooks it can just check for
> > > > > > > >   connector->state->crtc->state->self_refresh_active for sr state changes
> > > > > > > >   while the pipe is logically staying on
> > > > > > > > - the one asymmetric case due to this design where we disable the pipe
> > > > > > > >   harder has an awkward special hook, which gives us a great opportunity
> > > > > > > >   to explain why it's needed
> > > > > > > > - nothing changes for non-sr aware drivers
> > > > > > > > - also no need to duplicate sr state into connectors, since it's all
> > > > > > > >   fairly explit already in all three state transitions.
> > > > > > >
> > > > > > > To be fair, only one of these is exclusive to asymmetry, and it's the one that
> > > > > > > provides the opportunity to add a comment. If the sr functions are symmetric,
> > > > > > > the code becomes much more "normal" and less deserving of the explanation.
> > > > > > >
> > > > > > > The reason I would like to split out entry and exit is that it makes the driver
> > > > > > > code a bit easier to rationalize. Currently we need to check the state at the
> > > > > > > beginning of enable/disable to determine whether we want the full enable/disable
> > > > > > > or the psr exit/enter. So the sr_disable function would really just be plain
> > > > > > > old disable without the special casing at the top. In that case, we don't even
> > > > > > > need the separate function, we could just limit disable calls only on those
> > > > > > > objects which are effectively on (active || sr). That starts sounding a lot like
> > > > > > > what we already have here.
> > > > > > >
> > > > > > > Further, doing SR in enable/disable is really just legacy from v1 which tried to
> > > > > > > keep as much the same as possible. Now that we're "in it", I think it makes
> > > > > > > sense to go all in and make SR a first class citizen.
> > > > > >
> > > > > > Hm, question is: How many hooks do you need? Just something on the
> > > > > > connector, or on the encoder, or everywhere?
> > > > >
> > > > > bridge/encoder/crtc all do special things during SR transitions, I don't think
> > > > > connector is necessary. This is the same for any .sr_disable function, everyone
> > > > > would need to implement it.
> > > >
> > > > Hm, that's a lot of new callbacks ...
> > > >
> > > > > > And how do you handle the
> > > > > > various state transitions. On the disable side we have:
> > > > > > - active on -> active off, no sr (userspace disables crtc)
> > > > > > - active on, sr off -> active ooff, sr on (sr timer fires and suspends crtc)
> > > > > > - active off, sr on -> active off, sr off (userspace disable crtc
> > > > > > while crtc is in sr)
> > > > > > These are all "logical active on" -> "something" transitions where we
> > > > > > disable something (crtc, or display or both)
> > > > > >
> > > > > > So in a way you'd need 3 hooks here for the full matrix.
> > > > > > And they all
> > > > > > kinda disable something. On the enable side we have:
> > > > > > - active off, sr off -> active on, sr off (userspace enables crtc)
> > > > > > - active off, sr on -> active on, sr off (userspace does a pageflip, stops sr)
> > > > > > Here we either enable the crtc (display already on) or both. Since we
> > > > > > only go into sr with the timer there's no 3rd case of only enabling
> > > > > > the display. So still asymetric, even with lots more hooks.
> > > > >
> > > > > We don't need the (active off, sr on) -> (active off, sr off) (third) case
> > > > > above, it's the same as the first. Just doing a full disable is sufficient,
> > > > > so you would have symmetry in the enable/disable calls and asymmetry in the
> > > > > sr calls. This is similar to enabling a plane, or turning other HW features on
> > > > > while enabled. SR is after all just a feature of the hardware.
> > > >
> > > > Hm yeah I guess we can treat it like plane disabling, which implicitly
> > > > happens in crtc->disable too. Or the implicit plane enable in crtc->enable
> > > > (although that case doesn't exist for sr, since we never go directly into
> > > > sr).
> > > >
> > > > > > If you want the full matrix, there's going to be a _lot_ of hooks. I
> > > > > > think slightly more awkward driver, but less hooks is better. Hence
> > > > > > the slightly awkward middle ground of a special disable_from_sr hook.
> > > > > > But maybe there's a better option somewhere else ...
> > > > >
> > > > > There's really no reason to even have the sr_disable function. The .disable
> > > > > function in the driver will already need special casing to detect psr_entry
> > > > > vs full disable, so it'd be better to just call disable twice. The .sr_disable
> > > > > function would always just do a full disable (ie: the .disable implementation
> > > > > without the sr checks at the top).
> > > > >
> > > > > So the debate should be: add sr_enable/disable pair of hooks, or overload
> > > > > disable with asymmetry (current implementation).
> > > >
> > > > I guess that means we're back to no new hooks, and the driver just dtrt
> > > > in the existing hooks with the state transition bits we have? I thought
> > > > the issue with that is that we can't get at all the right bits, hence the
> > > > sr_disable special case hook.
> > > >
> > > > Or is your plan to roll out a full new set of hooks, equipped with
> > > > old/new_state for everything? I think we'd only need old/new_state for the
> > > > object at hand, since with the old_state you can get at drm_atomic_state,
> > > > which allows you to get anything else really.
> > >
> > > I don't think we even need to pass the state to the sr hooks, just add
> > >
> > > void self_refresh_enter(struct drm_<type> *<name>);
> > > void self_refresh_exit(struct drm_<type> *<name>);
> > >
> > > to the funcs vtable for crtc/encoder/bridge.
> > >
> > > Of course it's not _quite_ as straightforward as that :)
> > >
> > > With the current model, the powerdown/powerup order of components is implicitly
> > > broken. With this new model, it's much more obvious, this is easiest to
> > > illustrate with bridges, but it's true for crtcs and encoders as well.
> > >
> > > Assume you have the following bridge chain:
> > >
> > > ENC0 (not SR-aware)
> > >         -> BR0 (SR-aware)
> > >                 -> BR1 (not SR-aware)
> > >                         -> BR2 (SR-aware)
> > >                                 -> CON0
> > >
> > > An SR-enter transition would be:
> > >         BR2->self_refresh_enter
> > >         BR1->disable
> > >         BR0->self_refresh_enter
> > >         ENC0->disable
> > >         BR1->post_disable
> > >
> > > SR-exit is:
> > >         BR1->pre_enable
> > >         ENC0->enable
> > >         BR0->self_refresh_exit
> > >         BR1->enable
> > >         BR2->self_refresh_exit
> > >
> > > Disabling from SR becomes:
> > >         BR2->disable
> > >         BR0->disable
> > >         BR2->post_disable
> > >         BR0->post_disable
> > >
> > > So I'm starting to question falling back on disable. I think it was a fine
> > > choice when we would exit psr before disable (ie: v1), but I think it might be
> > > too complicated now. We could make BR2 and BR0 do the right thing on
> > > disable-from-SR, but I'm worried that mixing up the order for SR-unaware devices
> > > (ENC0/BR1) might cause issues.
> > >
> > > Perhaps we should scale this back and just treat self_refresh as its own thing
> > > and not go through the enable/disable path at all. Devices which are not
> > > SR-aware stay on (which has it's own issues if BR1 underflows because it's
> > > expecting video from BR0). Maybe we have to ensure the entire pipe is SR-aware
> > > before we do an SR-enter.
> >
> > Imo if the driver tries to enable SR on a pipe where some pieces
> > aren't SR aware, that's a driver bug.
>
> If you assume that most bridges (aside from the "bridges" that represent shared
> silicon IP) can be arbitrarily mixed with most other bridges and drivers, then
> yeah, this is unavoidable and not something that's easily fixed since we'd need
> to make all SR-aware or at the very least audit them to make sure they don't
> foul up if the things around them go to sleep.
>
> > And if you really want to
> > implement the above sequence (well, need to implement it), then I
> > agree that helpers aren't the thing you're looking for and you should
> > just roll your own modeset code.
> >
> > But we started all this assuming that you're hw isn't in the need of
> > the full state matrix with hooks for everything, hence that maybe a
> > helper would make sense. It feels a bit like the discussion lost
> > contact with the (driver) reality ...
> >
> > > Thoughts?
> >
> > ... so imo if we can help out drivers by repurposing the existing
> > hooks to cover most cases, with a few special cases in callbacks, then
> > we can roll these helpers. If that doesn't happen, then probably
> > better if each driver just rolls their own sr enter/exit code and
> > calls it done. It's not like we don't allow subclassing of states or
> > also vtable hooks where you could just add more of your own stuff as
> > you see fit. But I thought sr for most devices would amount to a)
> > shutting the pipe down b) some special casing to keep the display
> > alive and nothing else. But now it sounds like you need hooks for
> > everything, which probably doesnt make sense to cover in the helpers.
>
> For most everything upstream of the connector, you don't _need_ a hook since
> shutting them down is fine. However if your crtc takes a while to come back on
> (like with rockchip), then you start to want hooks everywhere to optimize
> things.
>
> I'll put this on the shelf and wait for a few more drivers to implement their
> own SR. Perhaps a pattern will emerge.

I did scroll through the rockchip implementation patches again. Adding
a few conditions seems not too onerous, repurposing the current hooks
works. And I think if we pimp the atomic hooks as ville suggested
(just the ones where we need it, we can be lazy), then I think we can
also get rid of the duplicated tracking in connectors and essentially
go with v2. So wondering a bit whether we managed to derail this
unecessarily? It seems to work ... And I think some state dependent
code flow is unavoidable anyway, whether you roll your own modeset
code or use the helpers. Most drivers have such "hacks" for something
somewhere.
-Daniel
diff mbox series

Patch

diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
index 58b375e47615..b0b71f73829f 100644
--- a/Documentation/gpu/drm-kms-helpers.rst
+++ b/Documentation/gpu/drm-kms-helpers.rst
@@ -107,6 +107,15 @@  fbdev Helper Functions Reference
 .. kernel-doc:: drivers/gpu/drm/drm_fb_helper.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:
+
 Framebuffer CMA Helper Functions Reference
 ==========================================
 
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index e630eccb951c..5b3a1e26ec94 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -38,7 +38,8 @@  drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_dsc.o drm_probe_helper
 		drm_kms_helper_common.o drm_dp_dual_mode_helper.o \
 		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_atomic_state_helper.o drm_damage_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
diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 5eb40130fafb..a06fe55b5ebf 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -379,6 +379,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);
@@ -881,6 +882,9 @@  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_active=%d\n", state->self_refresh_active);
+	drm_printf(p, "\tself_refresh_aware=%d\n", state->self_refresh_aware);
+	drm_printf(p, "\tself_refresh_changed=%d\n", state->self_refresh_changed);
 
 	if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK)
 		if (state->writeback_job && state->writeback_job->fb)
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index 2453678d1186..c659db105133 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -30,6 +30,7 @@ 
 #include <drm/drm_atomic_uapi.h>
 #include <drm/drm_plane_helper.h>
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_self_refresh_helper.h>
 #include <drm/drm_writeback.h>
 #include <drm/drm_damage_helper.h>
 #include <linux/dma-fence.h>
@@ -950,10 +951,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)
 {
@@ -974,7 +998,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;
 
@@ -1018,7 +1049,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;
@@ -2948,6 +2979,7 @@  int drm_atomic_helper_set_config(struct drm_mode_set *set,
 		return -ENOMEM;
 
 	state->acquire_ctx = ctx;
+
 	ret = __drm_atomic_helper_set_config(set, state);
 	if (ret != 0)
 		goto fail;
diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
index 4985384e51f6..ec90c527deed 100644
--- a/drivers/gpu/drm/drm_atomic_state_helper.c
+++ b/drivers/gpu/drm/drm_atomic_state_helper.c
@@ -105,6 +105,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);
 
@@ -370,6 +374,10 @@  __drm_atomic_helper_connector_duplicate_state(struct drm_connector *connector,
 
 	/* Don't copy over a writeback job, they are used only once */
 	state->writeback_job = NULL;
+
+	/* Self refresh should be canceled when a new update is available */
+	state->self_refresh_changed = state->self_refresh_active;
+	state->self_refresh_active = false;
 }
 EXPORT_SYMBOL(__drm_atomic_helper_connector_duplicate_state);
 
diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
index 4eb81f10bc54..d2085332172b 100644
--- a/drivers/gpu/drm/drm_atomic_uapi.c
+++ b/drivers/gpu/drm/drm_atomic_uapi.c
@@ -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)
@@ -785,7 +785,8 @@  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;
+		*val = state->self_refresh_active ? DRM_MODE_DPMS_ON :
+			connector->dpms;
 	} else if (property == config->tv_select_subconnector_property) {
 		*val = state->tv.subconnector;
 	} else if (property == config->tv_left_margin_property) {
diff --git a/drivers/gpu/drm/drm_self_refresh_helper.c b/drivers/gpu/drm/drm_self_refresh_helper.c
new file mode 100644
index 000000000000..a3afa031480e
--- /dev/null
+++ b/drivers/gpu/drm/drm_self_refresh_helper.c
@@ -0,0 +1,212 @@ 
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright (C) 2019 Google, Inc.
+ *
+ * Authors:
+ * Sean Paul <seanpaul@chromium.org>
+ */
+#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>
+#include <linux/bitops.h>
+#include <linux/slab.h>
+#include <linux/workqueue.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 registering and unregistering the SR helpers on load/unload.
+ *
+ * Once a crtc has enabled SR, 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 a flag set in
+ * state that tells you to disable/enable SR on the panel instead of power-
+ * cycling it.
+ *
+ * Drivers may choose to fully disable their crtc/encoder/bridge hardware, or
+ * they can use the "self_refresh_active" and "self_refresh_changed" flags in
+ * object state 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 SR mode.
+ */
+
+struct drm_self_refresh_state {
+	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_state *sr_state = container_of(
+				to_delayed_work(work),
+				struct drm_self_refresh_state, entry_work);
+	struct drm_crtc *crtc = sr_state->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;
+
+	crtc_state->active = false;
+	crtc_state->self_refresh_active = true;
+
+	for_each_new_connector_in_state(state, conn, conn_state, i) {
+		if (!conn_state->self_refresh_aware)
+			goto out;
+
+		conn_state->self_refresh_changed = true;
+		conn_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) {
+		for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
+			if (crtc_state->self_refresh_active) {
+				state->async_update = false;
+				break;
+			}
+		}
+	}
+	if (!state->allow_modeset) {
+		for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
+			if (crtc_state->self_refresh_active) {
+				state->allow_modeset = true;
+				break;
+			}
+		}
+	}
+
+	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
+		struct drm_self_refresh_state *sr_state;
+
+		/* Don't trigger the entry timer when we're already in SR */
+		if (crtc_state->self_refresh_active)
+			continue;
+
+		sr_state = crtc->self_refresh_state;
+		mod_delayed_work(system_wq, &sr_state->entry_work,
+			 msecs_to_jiffies(sr_state->entry_delay_ms));
+	}
+}
+EXPORT_SYMBOL(drm_self_refresh_helper_alter_state);
+
+/**
+ * drm_self_refresh_helper_register - Registers 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
+ */
+int drm_self_refresh_helper_register(struct drm_crtc *crtc,
+				     unsigned int entry_delay_ms)
+{
+	struct drm_self_refresh_state *sr_state = crtc->self_refresh_state;
+
+	/* Helper is already registered */
+	if (WARN_ON(sr_state))
+		return -EINVAL;
+
+	sr_state = kzalloc(sizeof(*sr_state), GFP_KERNEL);
+	if (!sr_state)
+		return -ENOMEM;
+
+	INIT_DELAYED_WORK(&sr_state->entry_work,
+			  drm_self_refresh_helper_entry_work);
+	sr_state->entry_delay_ms = entry_delay_ms;
+	sr_state->crtc = crtc;
+
+	crtc->self_refresh_state = sr_state;
+	return 0;
+}
+EXPORT_SYMBOL(drm_self_refresh_helper_register);
+
+/**
+ * drm_self_refresh_helper_unregister - Unregisters self refresh helpers
+ * @crtc: the crtc to unregister
+ */
+void drm_self_refresh_helper_unregister(struct drm_crtc *crtc)
+{
+	struct drm_self_refresh_state *sr_state = crtc->self_refresh_state;
+
+	/* Helper is already unregistered */
+	if (sr_state)
+		return;
+
+	crtc->self_refresh_state = NULL;
+
+	cancel_delayed_work_sync(&sr_state->entry_work);
+	kfree(sr_state);
+}
+EXPORT_SYMBOL(drm_self_refresh_helper_unregister);
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index 824a5ed4e216..1e9cab1da97a 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -944,4 +944,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_ */
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index c8061992d6cb..0ae7e812ec62 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -501,6 +501,37 @@  struct drm_connector_state {
 	/** @tv: TV connector state */
 	struct drm_tv_connector_state tv;
 
+	/**
+	 * @self_refresh_changed:
+	 *
+	 * Set true when self refresh status has changed. This is useful for
+	 * use in encoder/bridge enable where the old state is unavailable to
+	 * the driver and it needs to know whether the enable transition is a
+	 * full transition, or if it just needs to exit self refresh mode.
+	 */
+	bool self_refresh_changed;
+
+	/**
+	 * @self_refresh_active:
+	 *
+	 * Used by the self refresh (SR) helpers to denote when the display
+	 * should be self refreshing. If your connector is SR-capable, check
+	 * this flag in .disable(). If it is true, instead of shutting off the
+	 * panel, put it into self refreshing mode.
+	 */
+	bool self_refresh_active;
+
+	/**
+	 * @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.
+	 */
+	bool self_refresh_aware;
+
 	/**
 	 * @picture_aspect_ratio: Connector property to control the
 	 * HDMI infoframe aspect ratio setting.
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index f7c3022dbdf4..208d68129b4c 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -53,6 +53,7 @@  struct drm_mode_set;
 struct drm_file;
 struct drm_clip_rect;
 struct drm_printer;
+struct drm_self_refresh_state;
 struct device_node;
 struct dma_fence;
 struct edid;
@@ -299,6 +300,17 @@  struct drm_crtc_state {
 	 */
 	bool vrr_enabled;
 
+	/**
+	 * @self_refresh_active:
+	 *
+	 * Used by the self refresh helpers to denote when a self refresh
+	 * transition is occuring. This will be set on enable/disable callbacks
+	 * when self refresh is being enabled or disabled. In some cases, it may
+	 * not be desirable to fully shut off the crtc during self refresh.
+	 * CRTC's can inspect this flag and determine the best course of action.
+	 */
+	bool self_refresh_active;
+
 	/**
 	 * @event:
 	 *
@@ -1087,6 +1099,13 @@  struct drm_crtc {
 	 * The name of the CRTC's fence timeline.
 	 */
 	char timeline_name[32];
+
+	/**
+	 * @self_refresh_state: Holds the state for the self refresh helpers
+	 *
+	 * Initialized via drm_self_refresh_helper_register().
+	 */
+	struct drm_self_refresh_state *self_refresh_state;
 };
 
 /**
diff --git a/include/drm/drm_self_refresh_helper.h b/include/drm/drm_self_refresh_helper.h
new file mode 100644
index 000000000000..015dacb8a807
--- /dev/null
+++ b/include/drm/drm_self_refresh_helper.h
@@ -0,0 +1,23 @@ 
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright (C) 2019 Google, Inc.
+ *
+ * Authors:
+ * Sean Paul <seanpaul@chromium.org>
+ */
+#ifndef DRM_SELF_REFRESH_HELPER_H_
+#define DRM_SELF_REFRESH_HELPER_H_
+
+struct drm_atomic_state;
+struct drm_connector;
+struct drm_device;
+struct drm_self_refresh_state;
+struct drm_modeset_acquire_ctx;
+
+void drm_self_refresh_helper_alter_state(struct drm_atomic_state *state);
+
+int drm_self_refresh_helper_register(struct drm_crtc *crtc,
+				     unsigned int entry_delay_ms);
+
+void drm_self_refresh_helper_unregister(struct drm_crtc *crtc);
+#endif