dri-devel.lists.freedesktop.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
@ 2019-03-26 20:44 Sean Paul
  2019-03-26 20:44 ` [PATCH v2 2/5] drm/rockchip: Check for fast link training before enabling psr Sean Paul
                   ` (7 more replies)
  0 siblings, 8 replies; 30+ messages in thread
From: Sean Paul @ 2019-03-26 20:44 UTC (permalink / raw)
  To: dri-devel
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul, Sean Paul

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

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v2 2/5] drm/rockchip: Check for fast link training before enabling psr
  2019-03-26 20:44 [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers Sean Paul
@ 2019-03-26 20:44 ` Sean Paul
  2019-03-26 20:44 ` [PATCH v2 3/5] drm/rockchip: Use the helpers for PSR Sean Paul
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 30+ messages in thread
From: Sean Paul @ 2019-03-26 20:44 UTC (permalink / raw)
  To: dri-devel
  Cc: Zain Wang, David Airlie, Tomasz Figa, Sean Paul, Laurent Pinchart

From: Sean Paul <seanpaul@chromium.org>

Once we start shutting off the link during PSR, we're going to want fast
training to work. If the display doesn't support fast training, don't
enable psr.

Changes in v2:
- None

Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-3-sean@poorly.run

Cc: Zain Wang <wzz@rock-chips.com>
Cc: Tomasz Figa <tfiga@chromium.org>
Signed-off-by: Sean Paul <seanpaul@chromium.org>
---
 drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
index 225f5e5dd69b..af34554a5a02 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
@@ -1040,16 +1040,17 @@ static int analogix_dp_commit(struct analogix_dp_device *dp)
 	if (ret)
 		return ret;
 
+	/* Check whether panel supports fast training */
+	ret = analogix_dp_fast_link_train_detection(dp);
+	if (ret)
+		dp->psr_enable = false;
+
 	if (dp->psr_enable) {
 		ret = analogix_dp_enable_sink_psr(dp);
 		if (ret)
 			return ret;
 	}
 
-	/* Check whether panel supports fast training */
-	ret =  analogix_dp_fast_link_train_detection(dp);
-	if (ret)
-		dp->psr_enable = false;
 
 	return ret;
 }
-- 
Sean Paul, Software Engineer, Google / Chromium OS

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v2 3/5] drm/rockchip: Use the helpers for PSR
  2019-03-26 20:44 [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers Sean Paul
  2019-03-26 20:44 ` [PATCH v2 2/5] drm/rockchip: Check for fast link training before enabling psr Sean Paul
@ 2019-03-26 20:44 ` Sean Paul
  2019-03-29 18:51   ` Heiko Stübner
  2019-03-26 20:44 ` [PATCH v2 4/5] drm/rockchip: Don't fully disable vop on self refresh Sean Paul
                   ` (5 subsequent siblings)
  7 siblings, 1 reply; 30+ messages in thread
From: Sean Paul @ 2019-03-26 20:44 UTC (permalink / raw)
  To: dri-devel
  Cc: Zain Wang, David Airlie, Tomasz Figa, Sean Paul, Laurent Pinchart

From: Sean Paul <seanpaul@chromium.org>

Instead of rolling our own implementation for tracking when PSR should
be [in]active, use the new self refresh helpers to do the heavy lifting.

Changes in v2:
- updated to reflect changes made in the helpers

Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-4-sean@poorly.run

Cc: Zain Wang <wzz@rock-chips.com>
Cc: Tomasz Figa <tfiga@chromium.org>
Signed-off-by: Sean Paul <seanpaul@chromium.org>
---
 .../drm/bridge/analogix/analogix_dp_core.c    | 213 ++++++++-----
 .../drm/bridge/analogix/analogix_dp_core.h    |   2 +-
 drivers/gpu/drm/rockchip/Makefile             |   3 +-
 .../gpu/drm/rockchip/analogix_dp-rockchip.c   |  70 ++---
 drivers/gpu/drm/rockchip/rockchip_drm_fb.c    |  16 -
 drivers/gpu/drm/rockchip/rockchip_drm_psr.c   | 290 ------------------
 drivers/gpu/drm/rockchip/rockchip_drm_psr.h   |  30 --
 drivers/gpu/drm/rockchip/rockchip_drm_vop.c   |  33 +-
 include/drm/bridge/analogix_dp.h              |   4 +-
 9 files changed, 185 insertions(+), 476 deletions(-)
 delete mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_psr.c
 delete mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_psr.h

diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
index af34554a5a02..c503fe3bc3b3 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
@@ -106,63 +106,13 @@ static int analogix_dp_detect_hpd(struct analogix_dp_device *dp)
 	return 0;
 }
 
-int analogix_dp_psr_enabled(struct analogix_dp_device *dp)
+bool analogix_dp_self_refresh_changed(struct analogix_dp_device *dp)
 {
-
-	return dp->psr_enable;
+	return dp->connector.state->self_refresh_changed;
 }
-EXPORT_SYMBOL_GPL(analogix_dp_psr_enabled);
+EXPORT_SYMBOL_GPL(analogix_dp_self_refresh_changed);
 
-int analogix_dp_enable_psr(struct analogix_dp_device *dp)
-{
-	struct edp_vsc_psr psr_vsc;
-
-	if (!dp->psr_enable)
-		return 0;
-
-	/* Prepare VSC packet as per EDP 1.4 spec, Table 6.9 */
-	memset(&psr_vsc, 0, sizeof(psr_vsc));
-	psr_vsc.sdp_header.HB0 = 0;
-	psr_vsc.sdp_header.HB1 = 0x7;
-	psr_vsc.sdp_header.HB2 = 0x2;
-	psr_vsc.sdp_header.HB3 = 0x8;
-
-	psr_vsc.DB0 = 0;
-	psr_vsc.DB1 = EDP_VSC_PSR_STATE_ACTIVE | EDP_VSC_PSR_CRC_VALUES_VALID;
-
-	return analogix_dp_send_psr_spd(dp, &psr_vsc, true);
-}
-EXPORT_SYMBOL_GPL(analogix_dp_enable_psr);
-
-int analogix_dp_disable_psr(struct analogix_dp_device *dp)
-{
-	struct edp_vsc_psr psr_vsc;
-	int ret;
-
-	if (!dp->psr_enable)
-		return 0;
-
-	/* Prepare VSC packet as per EDP 1.4 spec, Table 6.9 */
-	memset(&psr_vsc, 0, sizeof(psr_vsc));
-	psr_vsc.sdp_header.HB0 = 0;
-	psr_vsc.sdp_header.HB1 = 0x7;
-	psr_vsc.sdp_header.HB2 = 0x2;
-	psr_vsc.sdp_header.HB3 = 0x8;
-
-	psr_vsc.DB0 = 0;
-	psr_vsc.DB1 = 0;
-
-	ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, DP_SET_POWER_D0);
-	if (ret != 1) {
-		dev_err(dp->dev, "Failed to set DP Power0 %d\n", ret);
-		return ret;
-	}
-
-	return analogix_dp_send_psr_spd(dp, &psr_vsc, false);
-}
-EXPORT_SYMBOL_GPL(analogix_dp_disable_psr);
-
-static int analogix_dp_detect_sink_psr(struct analogix_dp_device *dp)
+static bool analogix_dp_detect_sink_psr(struct analogix_dp_device *dp)
 {
 	unsigned char psr_version;
 	int ret;
@@ -170,14 +120,11 @@ static int analogix_dp_detect_sink_psr(struct analogix_dp_device *dp)
 	ret = drm_dp_dpcd_readb(&dp->aux, DP_PSR_SUPPORT, &psr_version);
 	if (ret != 1) {
 		dev_err(dp->dev, "failed to get PSR version, disable it\n");
-		return ret;
+		return false;
 	}
 
 	dev_dbg(dp->dev, "Panel PSR version : %x\n", psr_version);
-
-	dp->psr_enable = (psr_version & DP_PSR_IS_SUPPORTED) ? true : false;
-
-	return 0;
+	return psr_version & DP_PSR_IS_SUPPORTED;
 }
 
 static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp)
@@ -200,7 +147,7 @@ static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp)
 	}
 
 	/* Main-Link transmitter remains active during PSR active states */
-	psr_en = DP_PSR_MAIN_LINK_ACTIVE | DP_PSR_CRC_VERIFICATION;
+	psr_en = DP_PSR_CRC_VERIFICATION;
 	ret = drm_dp_dpcd_writeb(&dp->aux, DP_PSR_EN_CFG, psr_en);
 	if (ret != 1) {
 		dev_err(dp->dev, "failed to set panel psr\n");
@@ -208,8 +155,7 @@ static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp)
 	}
 
 	/* Enable psr function */
-	psr_en = DP_PSR_ENABLE | DP_PSR_MAIN_LINK_ACTIVE |
-		 DP_PSR_CRC_VERIFICATION;
+	psr_en = DP_PSR_ENABLE | DP_PSR_CRC_VERIFICATION;
 	ret = drm_dp_dpcd_writeb(&dp->aux, DP_PSR_EN_CFG, psr_en);
 	if (ret != 1) {
 		dev_err(dp->dev, "failed to set panel psr\n");
@@ -218,10 +164,11 @@ static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp)
 
 	analogix_dp_enable_psr_crc(dp);
 
+	dp->psr_supported = true;
+
 	return 0;
 end:
 	dev_err(dp->dev, "enable psr fail, force to disable psr\n");
-	dp->psr_enable = false;
 
 	return ret;
 }
@@ -1036,25 +983,90 @@ static int analogix_dp_commit(struct analogix_dp_device *dp)
 		}
 	}
 
-	ret = analogix_dp_detect_sink_psr(dp);
-	if (ret)
-		return ret;
-
 	/* Check whether panel supports fast training */
 	ret = analogix_dp_fast_link_train_detection(dp);
 	if (ret)
-		dp->psr_enable = false;
+		return ret;
 
-	if (dp->psr_enable) {
+	if (analogix_dp_detect_sink_psr(dp)) {
 		ret = analogix_dp_enable_sink_psr(dp);
 		if (ret)
 			return ret;
 	}
 
+	return ret;
+}
+
+static int analogix_dp_enable_psr(struct analogix_dp_device *dp)
+{
+	struct edp_vsc_psr psr_vsc;
+	int ret;
+	u8 sink;
+
+	ret = drm_dp_dpcd_readb(&dp->aux, DP_PSR_STATUS, &sink);
+	if (ret != 1)
+		DRM_DEV_ERROR(dp->dev, "Failed to read psr status %d\n", ret);
+	else if (sink == DP_PSR_SINK_ACTIVE_RFB)
+		return 0;
+
+	/* Prepare VSC packet as per EDP 1.4 spec, Table 6.9 */
+	memset(&psr_vsc, 0, sizeof(psr_vsc));
+	psr_vsc.sdp_header.HB0 = 0;
+	psr_vsc.sdp_header.HB1 = 0x7;
+	psr_vsc.sdp_header.HB2 = 0x2;
+	psr_vsc.sdp_header.HB3 = 0x8;
+	psr_vsc.DB0 = 0;
+	psr_vsc.DB1 = EDP_VSC_PSR_STATE_ACTIVE | EDP_VSC_PSR_CRC_VALUES_VALID;
+
+	ret = analogix_dp_send_psr_spd(dp, &psr_vsc, true);
+	if (!ret)
+		analogix_dp_set_analog_power_down(dp, POWER_ALL, true);
 
 	return ret;
 }
 
+static int analogix_dp_disable_psr(struct analogix_dp_device *dp)
+{
+	struct edp_vsc_psr psr_vsc;
+	int ret;
+	u8 sink;
+
+	analogix_dp_set_analog_power_down(dp, POWER_ALL, false);
+
+	ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, DP_SET_POWER_D0);
+	if (ret != 1) {
+		DRM_DEV_ERROR(dp->dev, "Failed to set DP Power0 %d\n", ret);
+		return ret;
+	}
+
+	ret = drm_dp_dpcd_readb(&dp->aux, DP_PSR_STATUS, &sink);
+	if (ret != 1) {
+		DRM_DEV_ERROR(dp->dev, "Failed to read psr status %d\n", ret);
+		return ret;
+	} else if (sink == DP_PSR_SINK_INACTIVE) {
+		DRM_DEV_ERROR(dp->dev, "sink inactive, skip disable psr");
+		return 0;
+	}
+
+	ret = analogix_dp_train_link(dp);
+	if (ret) {
+		DRM_DEV_ERROR(dp->dev, "Failed to train the link %d\n", ret);
+		return ret;
+	}
+
+	/* Prepare VSC packet as per EDP 1.4 spec, Table 6.9 */
+	memset(&psr_vsc, 0, sizeof(psr_vsc));
+	psr_vsc.sdp_header.HB0 = 0;
+	psr_vsc.sdp_header.HB1 = 0x7;
+	psr_vsc.sdp_header.HB2 = 0x2;
+	psr_vsc.sdp_header.HB3 = 0x8;
+
+	psr_vsc.DB0 = 0;
+	psr_vsc.DB1 = 0;
+
+	return analogix_dp_send_psr_spd(dp, &psr_vsc, true);
+}
+
 /*
  * This function is a bit of a catch-all for panel preparation, hopefully
  * simplifying the logic of functions that need to prepare/unprepare the panel
@@ -1145,9 +1157,23 @@ analogix_dp_best_encoder(struct drm_connector *connector)
 	return dp->encoder;
 }
 
+
+int analogix_dp_atomic_check(struct drm_connector *connector,
+			     struct drm_connector_state *state)
+{
+	struct analogix_dp_device *dp = to_dp(connector);
+
+	state->self_refresh_aware = true;
+	if (state->self_refresh_active && !dp->psr_supported)
+		return -EINVAL;
+
+	return 0;
+}
+
 static const struct drm_connector_helper_funcs analogix_dp_connector_helper_funcs = {
 	.get_modes = analogix_dp_get_modes,
 	.best_encoder = analogix_dp_best_encoder,
+	.atomic_check = analogix_dp_atomic_check,
 };
 
 static enum drm_connector_status
@@ -1244,6 +1270,10 @@ static void analogix_dp_bridge_pre_enable(struct drm_bridge *bridge)
 	struct analogix_dp_device *dp = bridge->driver_private;
 	int ret;
 
+	/* Don't touch the panel if we're coming back from PSR */
+	if (dp->connector.state->self_refresh_changed)
+		return;
+
 	ret = analogix_dp_prepare_panel(dp, true, true);
 	if (ret)
 		DRM_ERROR("failed to setup the panel ret = %d\n", ret);
@@ -1309,6 +1339,14 @@ static void analogix_dp_bridge_enable(struct drm_bridge *bridge)
 	struct analogix_dp_device *dp = bridge->driver_private;
 	int timeout_loop = 0;
 
+	/* Not a full enable, just disable PSR and continue */
+	if (dp->connector.state->self_refresh_changed) {
+		int ret = analogix_dp_disable_psr(dp);
+		if (ret)
+			DRM_ERROR("Failed to disable psr %d\n", ret);
+		return;
+	}
+
 	if (dp->dpms_mode == DRM_MODE_DPMS_ON)
 		return;
 
@@ -1356,11 +1394,37 @@ static void analogix_dp_bridge_disable(struct drm_bridge *bridge)
 	if (ret)
 		DRM_ERROR("failed to setup the panel ret = %d\n", ret);
 
-	dp->psr_enable = false;
 	dp->fast_train_enable = false;
+	dp->psr_supported = false;
 	dp->dpms_mode = DRM_MODE_DPMS_OFF;
 }
 
+static void analogix_dp_bridge_atomic_disable(struct drm_bridge *bridge)
+{
+	struct analogix_dp_device *dp = bridge->driver_private;
+	struct drm_connector_state *conn_state = dp->connector.state;
+
+	/* Don't do a full disable on PSR transitions */
+	if (conn_state->crtc && conn_state->self_refresh_changed)
+		return;
+
+	analogix_dp_bridge_disable(bridge);
+}
+
+static void analogix_dp_bridge_post_disable(struct drm_bridge *bridge)
+{
+	struct analogix_dp_device *dp = bridge->driver_private;
+	struct drm_connector_state *conn_state = dp->connector.state;
+	int ret;
+
+	if (conn_state->self_refresh_active) {
+		ret = analogix_dp_enable_psr(dp);
+		if (ret)
+			DRM_ERROR("Failed to enable psr (%d)\n", ret);
+		return;
+	}
+}
+
 static void analogix_dp_bridge_mode_set(struct drm_bridge *bridge,
 				const struct drm_display_mode *orig_mode,
 				const struct drm_display_mode *mode)
@@ -1440,16 +1504,11 @@ static void analogix_dp_bridge_mode_set(struct drm_bridge *bridge,
 		video->interlaced = true;
 }
 
-static void analogix_dp_bridge_nop(struct drm_bridge *bridge)
-{
-	/* do nothing */
-}
-
 static const struct drm_bridge_funcs analogix_dp_bridge_funcs = {
 	.pre_enable = analogix_dp_bridge_pre_enable,
 	.enable = analogix_dp_bridge_enable,
-	.disable = analogix_dp_bridge_disable,
-	.post_disable = analogix_dp_bridge_nop,
+	.disable = analogix_dp_bridge_atomic_disable,
+	.post_disable = analogix_dp_bridge_post_disable,
 	.mode_set = analogix_dp_bridge_mode_set,
 	.attach = analogix_dp_bridge_attach,
 };
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h
index 769255dc6e99..d937fa3d9ee8 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h
@@ -173,8 +173,8 @@ struct analogix_dp_device {
 	int			dpms_mode;
 	int			hpd_gpio;
 	bool                    force_hpd;
-	bool			psr_enable;
 	bool			fast_train_enable;
+	bool			psr_supported;
 
 	struct mutex		panel_lock;
 	bool			panel_is_modeset;
diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
index f6fc9d5dd0ad..a777726c23cb 100644
--- a/drivers/gpu/drm/rockchip/Makefile
+++ b/drivers/gpu/drm/rockchip/Makefile
@@ -4,8 +4,7 @@
 # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
 
 rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
-		rockchip_drm_gem.o rockchip_drm_psr.o \
-		rockchip_drm_vop.o rockchip_vop_reg.o
+		rockchip_drm_gem.o rockchip_drm_vop.o rockchip_vop_reg.o
 rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o
 
 rockchipdrm-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o
diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
index bc4423624209..8f42c38848db 100644
--- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
@@ -32,7 +32,6 @@
 #include <drm/bridge/analogix_dp.h>
 
 #include "rockchip_drm_drv.h"
-#include "rockchip_drm_psr.h"
 #include "rockchip_drm_vop.h"
 
 #define RK3288_GRF_SOC_CON6		0x25c
@@ -77,29 +76,6 @@ struct rockchip_dp_device {
 	struct analogix_dp_plat_data plat_data;
 };
 
-static int analogix_dp_psr_set(struct drm_encoder *encoder, bool enabled)
-{
-	struct rockchip_dp_device *dp = to_dp(encoder);
-	int ret;
-
-	if (!analogix_dp_psr_enabled(dp->adp))
-		return 0;
-
-	DRM_DEV_DEBUG(dp->dev, "%s PSR...\n", enabled ? "Entry" : "Exit");
-
-	ret = rockchip_drm_wait_vact_end(dp->encoder.crtc,
-					 PSR_WAIT_LINE_FLAG_TIMEOUT_MS);
-	if (ret) {
-		DRM_DEV_ERROR(dp->dev, "line flag interrupt did not arrive\n");
-		return -ETIMEDOUT;
-	}
-
-	if (enabled)
-		return analogix_dp_enable_psr(dp->adp);
-	else
-		return analogix_dp_disable_psr(dp->adp);
-}
-
 static int rockchip_dp_pre_init(struct rockchip_dp_device *dp)
 {
 	reset_control_assert(dp->rst);
@@ -130,21 +106,9 @@ static int rockchip_dp_poweron_start(struct analogix_dp_plat_data *plat_data)
 	return ret;
 }
 
-static int rockchip_dp_poweron_end(struct analogix_dp_plat_data *plat_data)
-{
-	struct rockchip_dp_device *dp = to_dp(plat_data);
-
-	return rockchip_drm_psr_inhibit_put(&dp->encoder);
-}
-
 static int rockchip_dp_powerdown(struct analogix_dp_plat_data *plat_data)
 {
 	struct rockchip_dp_device *dp = to_dp(plat_data);
-	int ret;
-
-	ret = rockchip_drm_psr_inhibit_get(&dp->encoder);
-	if (ret != 0)
-		return ret;
 
 	clk_disable_unprepare(dp->pclk);
 
@@ -190,6 +154,9 @@ static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder)
 	int ret;
 	u32 val;
 
+	if (analogix_dp_self_refresh_changed(dp->adp))
+		return;
+
 	ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, encoder);
 	if (ret < 0)
 		return;
@@ -214,9 +181,24 @@ static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder)
 	clk_disable_unprepare(dp->grfclk);
 }
 
-static void rockchip_dp_drm_encoder_nop(struct drm_encoder *encoder)
+static void rockchip_dp_drm_encoder_disable(struct drm_encoder *encoder)
 {
-	/* do nothing */
+	struct rockchip_dp_device *dp = to_dp(encoder);
+	struct drm_crtc *crtc;
+	int ret;
+
+	drm_for_each_crtc(crtc, dp->drm_dev) {
+		if (!(crtc->state->encoder_mask & drm_encoder_mask(encoder)))
+			continue;
+
+		if (!crtc->state->self_refresh_active)
+			continue;
+
+		ret = rockchip_drm_wait_vact_end(crtc,
+						 PSR_WAIT_LINE_FLAG_TIMEOUT_MS);
+		if (ret)
+			DRM_DEV_ERROR(dp->dev, "line flag irq timed out\n");
+	}
 }
 
 static int
@@ -246,7 +228,7 @@ static struct drm_encoder_helper_funcs rockchip_dp_encoder_helper_funcs = {
 	.mode_fixup = rockchip_dp_drm_encoder_mode_fixup,
 	.mode_set = rockchip_dp_drm_encoder_mode_set,
 	.enable = rockchip_dp_drm_encoder_enable,
-	.disable = rockchip_dp_drm_encoder_nop,
+	.disable = rockchip_dp_drm_encoder_disable,
 	.atomic_check = rockchip_dp_drm_encoder_atomic_check,
 };
 
@@ -338,23 +320,16 @@ static int rockchip_dp_bind(struct device *dev, struct device *master,
 
 	dp->plat_data.dev_type = dp->data->chip_type;
 	dp->plat_data.power_on_start = rockchip_dp_poweron_start;
-	dp->plat_data.power_on_end = rockchip_dp_poweron_end;
 	dp->plat_data.power_off = rockchip_dp_powerdown;
 	dp->plat_data.get_modes = rockchip_dp_get_modes;
 
-	ret = rockchip_drm_psr_register(&dp->encoder, analogix_dp_psr_set);
-	if (ret < 0)
-		goto err_cleanup_encoder;
-
 	dp->adp = analogix_dp_bind(dev, dp->drm_dev, &dp->plat_data);
 	if (IS_ERR(dp->adp)) {
 		ret = PTR_ERR(dp->adp);
-		goto err_unreg_psr;
+		goto err_cleanup_encoder;
 	}
 
 	return 0;
-err_unreg_psr:
-	rockchip_drm_psr_unregister(&dp->encoder);
 err_cleanup_encoder:
 	dp->encoder.funcs->destroy(&dp->encoder);
 	return ret;
@@ -366,7 +341,6 @@ static void rockchip_dp_unbind(struct device *dev, struct device *master,
 	struct rockchip_dp_device *dp = dev_get_drvdata(dev);
 
 	analogix_dp_unbind(dp->adp);
-	rockchip_drm_psr_unregister(&dp->encoder);
 	dp->encoder.funcs->destroy(&dp->encoder);
 
 	dp->adp = ERR_PTR(-ENODEV);
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
index 97438bbbe389..7e121875e3c9 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
@@ -23,22 +23,10 @@
 #include "rockchip_drm_drv.h"
 #include "rockchip_drm_fb.h"
 #include "rockchip_drm_gem.h"
-#include "rockchip_drm_psr.h"
-
-static int rockchip_drm_fb_dirty(struct drm_framebuffer *fb,
-				 struct drm_file *file,
-				 unsigned int flags, unsigned int color,
-				 struct drm_clip_rect *clips,
-				 unsigned int num_clips)
-{
-	rockchip_drm_psr_flush_all(fb->dev);
-	return 0;
-}
 
 static const struct drm_framebuffer_funcs rockchip_drm_fb_funcs = {
 	.destroy       = drm_gem_fb_destroy,
 	.create_handle = drm_gem_fb_create_handle,
-	.dirty	       = rockchip_drm_fb_dirty,
 };
 
 static struct drm_framebuffer *
@@ -132,8 +120,6 @@ rockchip_atomic_helper_commit_tail_rpm(struct drm_atomic_state *old_state)
 {
 	struct drm_device *dev = old_state->dev;
 
-	rockchip_drm_psr_inhibit_get_state(old_state);
-
 	drm_atomic_helper_commit_modeset_disables(dev, old_state);
 
 	drm_atomic_helper_commit_modeset_enables(dev, old_state);
@@ -141,8 +127,6 @@ rockchip_atomic_helper_commit_tail_rpm(struct drm_atomic_state *old_state)
 	drm_atomic_helper_commit_planes(dev, old_state,
 					DRM_PLANE_COMMIT_ACTIVE_ONLY);
 
-	rockchip_drm_psr_inhibit_put_state(old_state);
-
 	drm_atomic_helper_commit_hw_done(old_state);
 
 	drm_atomic_helper_wait_for_vblanks(dev, old_state);
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_psr.c b/drivers/gpu/drm/rockchip/rockchip_drm_psr.c
deleted file mode 100644
index a0c8bd235b67..000000000000
--- a/drivers/gpu/drm/rockchip/rockchip_drm_psr.c
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
- * Author: Yakir Yang <ykk@rock-chips.com>
- *
- * This software is licensed under the terms of the GNU General Public
- * License version 2, as published by the Free Software Foundation, and
- * may be copied, distributed, and modified under those terms.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include <drm/drmP.h>
-#include <drm/drm_atomic.h>
-#include <drm/drm_probe_helper.h>
-
-#include "rockchip_drm_drv.h"
-#include "rockchip_drm_psr.h"
-
-#define PSR_FLUSH_TIMEOUT_MS	100
-
-struct psr_drv {
-	struct list_head	list;
-	struct drm_encoder	*encoder;
-
-	struct mutex		lock;
-	int			inhibit_count;
-	bool			enabled;
-
-	struct delayed_work	flush_work;
-
-	int (*set)(struct drm_encoder *encoder, bool enable);
-};
-
-static struct psr_drv *find_psr_by_encoder(struct drm_encoder *encoder)
-{
-	struct rockchip_drm_private *drm_drv = encoder->dev->dev_private;
-	struct psr_drv *psr;
-
-	mutex_lock(&drm_drv->psr_list_lock);
-	list_for_each_entry(psr, &drm_drv->psr_list, list) {
-		if (psr->encoder == encoder)
-			goto out;
-	}
-	psr = ERR_PTR(-ENODEV);
-
-out:
-	mutex_unlock(&drm_drv->psr_list_lock);
-	return psr;
-}
-
-static int psr_set_state_locked(struct psr_drv *psr, bool enable)
-{
-	int ret;
-
-	if (psr->inhibit_count > 0)
-		return -EINVAL;
-
-	if (enable == psr->enabled)
-		return 0;
-
-	ret = psr->set(psr->encoder, enable);
-	if (ret)
-		return ret;
-
-	psr->enabled = enable;
-	return 0;
-}
-
-static void psr_flush_handler(struct work_struct *work)
-{
-	struct psr_drv *psr = container_of(to_delayed_work(work),
-					   struct psr_drv, flush_work);
-
-	mutex_lock(&psr->lock);
-	psr_set_state_locked(psr, true);
-	mutex_unlock(&psr->lock);
-}
-
-/**
- * rockchip_drm_psr_inhibit_put - release PSR inhibit on given encoder
- * @encoder: encoder to obtain the PSR encoder
- *
- * Decrements PSR inhibit count on given encoder. Should be called only
- * for a PSR inhibit count increment done before. If PSR inhibit counter
- * reaches zero, PSR flush work is scheduled to make the hardware enter
- * PSR mode in PSR_FLUSH_TIMEOUT_MS.
- *
- * Returns:
- * Zero on success, negative errno on failure.
- */
-int rockchip_drm_psr_inhibit_put(struct drm_encoder *encoder)
-{
-	struct psr_drv *psr = find_psr_by_encoder(encoder);
-
-	if (IS_ERR(psr))
-		return PTR_ERR(psr);
-
-	mutex_lock(&psr->lock);
-	--psr->inhibit_count;
-	WARN_ON(psr->inhibit_count < 0);
-	if (!psr->inhibit_count)
-		mod_delayed_work(system_wq, &psr->flush_work,
-				 PSR_FLUSH_TIMEOUT_MS);
-	mutex_unlock(&psr->lock);
-
-	return 0;
-}
-EXPORT_SYMBOL(rockchip_drm_psr_inhibit_put);
-
-void rockchip_drm_psr_inhibit_get_state(struct drm_atomic_state *state)
-{
-	struct drm_crtc *crtc;
-	struct drm_crtc_state *crtc_state;
-	struct drm_encoder *encoder;
-	u32 encoder_mask = 0;
-	int i;
-
-	for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
-		encoder_mask |= crtc_state->encoder_mask;
-		encoder_mask |= crtc->state->encoder_mask;
-	}
-
-	drm_for_each_encoder_mask(encoder, state->dev, encoder_mask)
-		rockchip_drm_psr_inhibit_get(encoder);
-}
-EXPORT_SYMBOL(rockchip_drm_psr_inhibit_get_state);
-
-void rockchip_drm_psr_inhibit_put_state(struct drm_atomic_state *state)
-{
-	struct drm_crtc *crtc;
-	struct drm_crtc_state *crtc_state;
-	struct drm_encoder *encoder;
-	u32 encoder_mask = 0;
-	int i;
-
-	for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
-		encoder_mask |= crtc_state->encoder_mask;
-		encoder_mask |= crtc->state->encoder_mask;
-	}
-
-	drm_for_each_encoder_mask(encoder, state->dev, encoder_mask)
-		rockchip_drm_psr_inhibit_put(encoder);
-}
-EXPORT_SYMBOL(rockchip_drm_psr_inhibit_put_state);
-
-/**
- * rockchip_drm_psr_inhibit_get - acquire PSR inhibit on given encoder
- * @encoder: encoder to obtain the PSR encoder
- *
- * Increments PSR inhibit count on given encoder. This function guarantees
- * that after it returns PSR is turned off on given encoder and no PSR-related
- * hardware state change occurs at least until a matching call to
- * rockchip_drm_psr_inhibit_put() is done.
- *
- * Returns:
- * Zero on success, negative errno on failure.
- */
-int rockchip_drm_psr_inhibit_get(struct drm_encoder *encoder)
-{
-	struct psr_drv *psr = find_psr_by_encoder(encoder);
-
-	if (IS_ERR(psr))
-		return PTR_ERR(psr);
-
-	mutex_lock(&psr->lock);
-	psr_set_state_locked(psr, false);
-	++psr->inhibit_count;
-	mutex_unlock(&psr->lock);
-	cancel_delayed_work_sync(&psr->flush_work);
-
-	return 0;
-}
-EXPORT_SYMBOL(rockchip_drm_psr_inhibit_get);
-
-static void rockchip_drm_do_flush(struct psr_drv *psr)
-{
-	cancel_delayed_work_sync(&psr->flush_work);
-
-	mutex_lock(&psr->lock);
-	if (!psr_set_state_locked(psr, false))
-		mod_delayed_work(system_wq, &psr->flush_work,
-				 PSR_FLUSH_TIMEOUT_MS);
-	mutex_unlock(&psr->lock);
-}
-
-/**
- * rockchip_drm_psr_flush_all - force to flush all registered PSR encoders
- * @dev: drm device
- *
- * Disable the PSR function for all registered encoders, and then enable the
- * PSR function back after PSR_FLUSH_TIMEOUT. If encoder PSR state have been
- * changed during flush time, then keep the state no change after flush
- * timeout.
- *
- * Returns:
- * Zero on success, negative errno on failure.
- */
-void rockchip_drm_psr_flush_all(struct drm_device *dev)
-{
-	struct rockchip_drm_private *drm_drv = dev->dev_private;
-	struct psr_drv *psr;
-
-	mutex_lock(&drm_drv->psr_list_lock);
-	list_for_each_entry(psr, &drm_drv->psr_list, list)
-		rockchip_drm_do_flush(psr);
-	mutex_unlock(&drm_drv->psr_list_lock);
-}
-EXPORT_SYMBOL(rockchip_drm_psr_flush_all);
-
-/**
- * rockchip_drm_psr_register - register encoder to psr driver
- * @encoder: encoder that obtain the PSR function
- * @psr_set: call back to set PSR state
- *
- * The function returns with PSR inhibit counter initialized with one
- * and the caller (typically encoder driver) needs to call
- * rockchip_drm_psr_inhibit_put() when it becomes ready to accept PSR
- * enable request.
- *
- * Returns:
- * Zero on success, negative errno on failure.
- */
-int rockchip_drm_psr_register(struct drm_encoder *encoder,
-			int (*psr_set)(struct drm_encoder *, bool enable))
-{
-	struct rockchip_drm_private *drm_drv;
-	struct psr_drv *psr;
-
-	if (!encoder || !psr_set)
-		return -EINVAL;
-
-	drm_drv = encoder->dev->dev_private;
-
-	psr = kzalloc(sizeof(struct psr_drv), GFP_KERNEL);
-	if (!psr)
-		return -ENOMEM;
-
-	INIT_DELAYED_WORK(&psr->flush_work, psr_flush_handler);
-	mutex_init(&psr->lock);
-
-	psr->inhibit_count = 1;
-	psr->enabled = false;
-	psr->encoder = encoder;
-	psr->set = psr_set;
-
-	mutex_lock(&drm_drv->psr_list_lock);
-	list_add_tail(&psr->list, &drm_drv->psr_list);
-	mutex_unlock(&drm_drv->psr_list_lock);
-
-	return 0;
-}
-EXPORT_SYMBOL(rockchip_drm_psr_register);
-
-/**
- * rockchip_drm_psr_unregister - unregister encoder to psr driver
- * @encoder: encoder that obtain the PSR function
- * @psr_set: call back to set PSR state
- *
- * It is expected that the PSR inhibit counter is 1 when this function is
- * called, which corresponds to a state when related encoder has been
- * disconnected from any CRTCs and its driver called
- * rockchip_drm_psr_inhibit_get() to stop the PSR logic.
- *
- * Returns:
- * Zero on success, negative errno on failure.
- */
-void rockchip_drm_psr_unregister(struct drm_encoder *encoder)
-{
-	struct rockchip_drm_private *drm_drv = encoder->dev->dev_private;
-	struct psr_drv *psr, *n;
-
-	mutex_lock(&drm_drv->psr_list_lock);
-	list_for_each_entry_safe(psr, n, &drm_drv->psr_list, list) {
-		if (psr->encoder == encoder) {
-			/*
-			 * Any other value would mean that the encoder
-			 * is still in use.
-			 */
-			WARN_ON(psr->inhibit_count != 1);
-
-			list_del(&psr->list);
-			kfree(psr);
-		}
-	}
-	mutex_unlock(&drm_drv->psr_list_lock);
-}
-EXPORT_SYMBOL(rockchip_drm_psr_unregister);
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_psr.h b/drivers/gpu/drm/rockchip/rockchip_drm_psr.h
deleted file mode 100644
index 25350ba3237b..000000000000
--- a/drivers/gpu/drm/rockchip/rockchip_drm_psr.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
- * Author: Yakir Yang <ykk@rock-chips.com>
- *
- * This software is licensed under the terms of the GNU General Public
- * License version 2, as published by the Free Software Foundation, and
- * may be copied, distributed, and modified under those terms.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#ifndef __ROCKCHIP_DRM_PSR___
-#define __ROCKCHIP_DRM_PSR___
-
-void rockchip_drm_psr_flush_all(struct drm_device *dev);
-
-int rockchip_drm_psr_inhibit_put(struct drm_encoder *encoder);
-int rockchip_drm_psr_inhibit_get(struct drm_encoder *encoder);
-
-void rockchip_drm_psr_inhibit_get_state(struct drm_atomic_state *state);
-void rockchip_drm_psr_inhibit_put_state(struct drm_atomic_state *state);
-
-int rockchip_drm_psr_register(struct drm_encoder *encoder,
-			int (*psr_set)(struct drm_encoder *, bool enable));
-void rockchip_drm_psr_unregister(struct drm_encoder *encoder);
-
-#endif /* __ROCKCHIP_DRM_PSR__ */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
index c7d4c6073ea5..21fccda70356 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
@@ -21,6 +21,7 @@
 #include <drm/drm_gem_framebuffer_helper.h>
 #include <drm/drm_plane_helper.h>
 #include <drm/drm_probe_helper.h>
+#include <drm/drm_self_refresh_helper.h>
 #ifdef CONFIG_DRM_ANALOGIX_DP
 #include <drm/bridge/analogix_dp.h>
 #endif
@@ -42,10 +43,11 @@
 #include "rockchip_drm_drv.h"
 #include "rockchip_drm_gem.h"
 #include "rockchip_drm_fb.h"
-#include "rockchip_drm_psr.h"
 #include "rockchip_drm_vop.h"
 #include "rockchip_rgb.h"
 
+#define VOP_SELF_REFRESH_ENTRY_DELAY_MS 100
+
 #define VOP_WIN_SET(vop, win, name, v) \
 		vop_reg_set(vop, &win->phy->name, win->base, ~0, v, #name)
 #define VOP_SCL_SET(vop, win, name, v) \
@@ -541,7 +543,7 @@ static void vop_core_clks_disable(struct vop *vop)
 	clk_disable(vop->hclk);
 }
 
-static int vop_enable(struct drm_crtc *crtc)
+static int vop_enable(struct drm_crtc *crtc, struct drm_crtc_state *old_state)
 {
 	struct vop *vop = to_vop(crtc);
 	int ret, i;
@@ -581,12 +583,18 @@ static int vop_enable(struct drm_crtc *crtc)
 	 * We need to make sure that all windows are disabled before we
 	 * enable the crtc. Otherwise we might try to scan from a destroyed
 	 * buffer later.
+	 *
+	 * In the case of enable-after-PSR, we don't need to worry about this
+	 * case since the buffer is guaranteed to be valid and disabling the
+	 * window will result in screen glitches on PSR exit.
 	 */
-	for (i = 0; i < vop->data->win_size; i++) {
-		struct vop_win *vop_win = &vop->win[i];
-		const struct vop_win_data *win = vop_win->data;
+	if (!old_state || !old_state->self_refresh_active) {
+		for (i = 0; i < vop->data->win_size; i++) {
+			struct vop_win *vop_win = &vop->win[i];
+			const struct vop_win_data *win = vop_win->data;
 
-		VOP_WIN_SET(vop, win, enable, 0);
+			VOP_WIN_SET(vop, win, enable, 0);
+		}
 	}
 	spin_unlock(&vop->reg_lock);
 
@@ -937,12 +945,10 @@ static void vop_plane_atomic_async_update(struct drm_plane *plane,
 	}
 
 	if (vop->is_enabled) {
-		rockchip_drm_psr_inhibit_get_state(new_state->state);
 		vop_plane_atomic_update(plane, plane->state);
 		spin_lock(&vop->reg_lock);
 		vop_cfg_done(vop);
 		spin_unlock(&vop->reg_lock);
-		rockchip_drm_psr_inhibit_put_state(new_state->state);
 	}
 
 	plane->funcs->atomic_destroy_state(plane, plane_state);
@@ -1035,7 +1041,7 @@ static void vop_crtc_atomic_enable(struct drm_crtc *crtc,
 
 	WARN_ON(vop->event);
 
-	ret = vop_enable(crtc);
+	ret = vop_enable(crtc, old_state);
 	if (ret) {
 		mutex_unlock(&vop->vop_lock);
 		DRM_DEV_ERROR(vop->dev, "Failed to enable vop (%d)\n", ret);
@@ -1509,6 +1515,13 @@ static int vop_create_crtc(struct vop *vop)
 	init_completion(&vop->line_flag_completion);
 	crtc->port = port;
 
+	ret = drm_self_refresh_helper_register(crtc,
+					       VOP_SELF_REFRESH_ENTRY_DELAY_MS);
+	if (ret)
+		DRM_DEV_DEBUG_KMS(vop->dev,
+			"Failed to register %s with SR helpers %d, ignoring\n",
+			crtc->name, ret);
+
 	return 0;
 
 err_cleanup_crtc:
@@ -1526,6 +1539,8 @@ static void vop_destroy_crtc(struct vop *vop)
 	struct drm_device *drm_dev = vop->drm_dev;
 	struct drm_plane *plane, *tmp;
 
+	drm_self_refresh_helper_unregister(crtc);
+
 	of_node_put(crtc->port);
 
 	/*
diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h
index 475b706b49de..b6cc654143d7 100644
--- a/include/drm/bridge/analogix_dp.h
+++ b/include/drm/bridge/analogix_dp.h
@@ -42,9 +42,7 @@ struct analogix_dp_plat_data {
 			 struct drm_connector *);
 };
 
-int analogix_dp_psr_enabled(struct analogix_dp_device *dp);
-int analogix_dp_enable_psr(struct analogix_dp_device *dp);
-int analogix_dp_disable_psr(struct analogix_dp_device *dp);
+bool analogix_dp_self_refresh_changed(struct analogix_dp_device *dp);
 
 int analogix_dp_resume(struct analogix_dp_device *dp);
 int analogix_dp_suspend(struct analogix_dp_device *dp);
-- 
Sean Paul, Software Engineer, Google / Chromium OS

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v2 4/5] drm/rockchip: Don't fully disable vop on self refresh
  2019-03-26 20:44 [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers Sean Paul
  2019-03-26 20:44 ` [PATCH v2 2/5] drm/rockchip: Check for fast link training before enabling psr Sean Paul
  2019-03-26 20:44 ` [PATCH v2 3/5] drm/rockchip: Use the helpers for PSR Sean Paul
@ 2019-03-26 20:44 ` Sean Paul
  2019-03-26 20:44 ` [PATCH v2 5/5] drm/rockchip: Use drm_atomic_helper_commit_tail_rpm Sean Paul
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 30+ messages in thread
From: Sean Paul @ 2019-03-26 20:44 UTC (permalink / raw)
  To: dri-devel
  Cc: Zain Wang, David Airlie, Tomasz Figa, Sean Paul, Kristian H . Kristensen

From: Sean Paul <seanpaul@chromium.org>

Instead of fully disabling and re-enabling the vop on self refresh
transitions, only disable the active windows. This will speed up
self refresh exits substantially and is still a power-savings win.

This patch integrates portions of Zain's patch from here:
https://patchwork.kernel.org/patch/9615063/

Changes in v2:
- None

Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-5-sean@poorly.run

Cc: Zain Wang <wzz@rock-chips.com>
Cc: Tomasz Figa <tfiga@chromium.org>
Cc: Kristian H. Kristensen <hoegsberg@chromium.org>
Signed-off-by: Sean Paul <seanpaul@chromium.org>
---
 drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 38 +++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
index 21fccda70356..a1e8a9a7bdd0 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
@@ -134,6 +134,7 @@ struct vop {
 	bool is_enabled;
 
 	struct completion dsp_hold_completion;
+	unsigned int win_enabled;
 
 	/* protected by dev->event_lock */
 	struct drm_pending_vblank_event *event;
@@ -594,6 +595,7 @@ static int vop_enable(struct drm_crtc *crtc, struct drm_crtc_state *old_state)
 			const struct vop_win_data *win = vop_win->data;
 
 			VOP_WIN_SET(vop, win, enable, 0);
+			vop->win_enabled &= ~BIT(i);
 		}
 	}
 	spin_unlock(&vop->reg_lock);
@@ -624,6 +626,25 @@ static int vop_enable(struct drm_crtc *crtc, struct drm_crtc_state *old_state)
 	return ret;
 }
 
+static void rockchip_drm_set_win_enabled(struct drm_crtc *crtc, bool enabled)
+{
+        struct vop *vop = to_vop(crtc);
+        int i;
+
+        spin_lock(&vop->reg_lock);
+
+        for (i = 0; i < vop->data->win_size; i++) {
+                struct vop_win *vop_win = &vop->win[i];
+                const struct vop_win_data *win = vop_win->data;
+
+                VOP_WIN_SET(vop, win, enable,
+                            enabled && (vop->win_enabled & BIT(i)));
+        }
+        vop_cfg_done(vop);
+
+        spin_unlock(&vop->reg_lock);
+}
+
 static void vop_crtc_atomic_disable(struct drm_crtc *crtc,
 				    struct drm_crtc_state *old_state)
 {
@@ -631,9 +652,15 @@ static void vop_crtc_atomic_disable(struct drm_crtc *crtc,
 
 	WARN_ON(vop->event);
 
+	if (crtc->state->self_refresh_active)
+		rockchip_drm_set_win_enabled(crtc, false);
+
 	mutex_lock(&vop->vop_lock);
 	drm_crtc_vblank_off(crtc);
 
+	if (crtc->state->self_refresh_active)
+		goto out;
+
 	/*
 	 * Vop standby will take effect at end of current frame,
 	 * if dsp hold valid irq happen, it means standby complete.
@@ -664,6 +691,8 @@ static void vop_crtc_atomic_disable(struct drm_crtc *crtc,
 	clk_disable(vop->dclk);
 	vop_core_clks_disable(vop);
 	pm_runtime_put(vop->dev);
+
+out:
 	mutex_unlock(&vop->vop_lock);
 
 	if (crtc->state->event && !crtc->state->active) {
@@ -744,6 +773,7 @@ static void vop_plane_atomic_disable(struct drm_plane *plane,
 	spin_lock(&vop->reg_lock);
 
 	VOP_WIN_SET(vop, win, enable, 0);
+	vop->win_enabled &= ~BIT(VOP_WIN_TO_INDEX(vop_win));
 
 	spin_unlock(&vop->reg_lock);
 }
@@ -882,6 +912,7 @@ static void vop_plane_atomic_update(struct drm_plane *plane,
 	}
 
 	VOP_WIN_SET(vop, win, enable, 1);
+	vop->win_enabled |= BIT(win_index);
 	spin_unlock(&vop->reg_lock);
 }
 
@@ -1037,6 +1068,12 @@ static void vop_crtc_atomic_enable(struct drm_crtc *crtc,
 	uint32_t pin_pol, val;
 	int ret;
 
+	if (old_state && old_state->self_refresh_active) {
+		drm_crtc_vblank_on(crtc);
+		rockchip_drm_set_win_enabled(crtc, true);
+		return;
+	}
+
 	mutex_lock(&vop->vop_lock);
 
 	WARN_ON(vop->event);
@@ -1639,6 +1676,7 @@ static int vop_initial(struct vop *vop)
 		VOP_WIN_SET(vop, win, channel, (channel + 1) << 4 | channel);
 		VOP_WIN_SET(vop, win, enable, 0);
 		VOP_WIN_SET(vop, win, gate, 1);
+		vop->win_enabled &= ~BIT(i);
 	}
 
 	vop_cfg_done(vop);
-- 
Sean Paul, Software Engineer, Google / Chromium OS

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* [PATCH v2 5/5] drm/rockchip: Use drm_atomic_helper_commit_tail_rpm
  2019-03-26 20:44 [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers Sean Paul
                   ` (2 preceding siblings ...)
  2019-03-26 20:44 ` [PATCH v2 4/5] drm/rockchip: Don't fully disable vop on self refresh Sean Paul
@ 2019-03-26 20:44 ` Sean Paul
  2019-03-27 18:15 ` [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers Daniel Vetter
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 30+ messages in thread
From: Sean Paul @ 2019-03-26 20:44 UTC (permalink / raw)
  To: dri-devel; +Cc: Zain Wang, David Airlie, Tomasz Figa, Sean Paul

From: Sean Paul <seanpaul@chromium.org>

Now that we use the drm psr helpers, we no longer need to hand-roll our
atomic_commit_tail implementation. So use the helper

Changes in v2:
- None

Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-6-sean@poorly.run

Cc: Zain Wang <wzz@rock-chips.com>
Cc: Tomasz Figa <tfiga@chromium.org>
Signed-off-by: Sean Paul <seanpaul@chromium.org>
---
 drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 21 +--------------------
 1 file changed, 1 insertion(+), 20 deletions(-)

diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
index 7e121875e3c9..75198df2b022 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
@@ -115,27 +115,8 @@ rockchip_user_fb_create(struct drm_device *dev, struct drm_file *file_priv,
 	return ERR_PTR(ret);
 }
 
-static void
-rockchip_atomic_helper_commit_tail_rpm(struct drm_atomic_state *old_state)
-{
-	struct drm_device *dev = old_state->dev;
-
-	drm_atomic_helper_commit_modeset_disables(dev, old_state);
-
-	drm_atomic_helper_commit_modeset_enables(dev, old_state);
-
-	drm_atomic_helper_commit_planes(dev, old_state,
-					DRM_PLANE_COMMIT_ACTIVE_ONLY);
-
-	drm_atomic_helper_commit_hw_done(old_state);
-
-	drm_atomic_helper_wait_for_vblanks(dev, old_state);
-
-	drm_atomic_helper_cleanup_planes(dev, old_state);
-}
-
 static const struct drm_mode_config_helper_funcs rockchip_mode_config_helpers = {
-	.atomic_commit_tail = rockchip_atomic_helper_commit_tail_rpm,
+	.atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
 };
 
 static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = {
-- 
Sean Paul, Software Engineer, Google / Chromium OS

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-03-26 20:44 [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers Sean Paul
                   ` (3 preceding siblings ...)
  2019-03-26 20:44 ` [PATCH v2 5/5] drm/rockchip: Use drm_atomic_helper_commit_tail_rpm Sean Paul
@ 2019-03-27 18:15 ` Daniel Vetter
  2019-03-28 21:03   ` Sean Paul
  2019-03-28 14:42 ` Dan Carpenter
                   ` (2 subsequent siblings)
  7 siblings, 1 reply; 30+ messages in thread
From: Daniel Vetter @ 2019-03-27 18:15 UTC (permalink / raw)
  To: Sean Paul
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul, dri-devel

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
-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-03-26 20:44 [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers Sean Paul
                   ` (4 preceding siblings ...)
  2019-03-27 18:15 ` [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers Daniel Vetter
@ 2019-03-28 14:42 ` Dan Carpenter
  2019-04-02  8:55 ` Neil Armstrong
  2019-04-02  9:53 ` Daniel Vetter
  7 siblings, 0 replies; 30+ messages in thread
From: Dan Carpenter @ 2019-03-28 14:42 UTC (permalink / raw)
  To: kbuild
  Cc: Zain Wang, David Airlie, dri-devel, Jose Souza, Tomasz Figa,
	Maxime Ripard, Sean Paul, kbuild-all, Sean Paul

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  

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-03-27 18:15 ` [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers Daniel Vetter
@ 2019-03-28 21:03   ` Sean Paul
  2019-03-29  8:21     ` Daniel Vetter
  0 siblings, 1 reply; 30+ messages in thread
From: Sean Paul @ 2019-03-28 21:03 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul, dri-devel, Sean Paul

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

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-03-28 21:03   ` Sean Paul
@ 2019-03-29  8:21     ` Daniel Vetter
  2019-03-29 13:16       ` Sean Paul
  0 siblings, 1 reply; 30+ messages in thread
From: Daniel Vetter @ 2019-03-29  8:21 UTC (permalink / raw)
  To: Sean Paul
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul, dri-devel

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
-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-03-29  8:21     ` Daniel Vetter
@ 2019-03-29 13:16       ` Sean Paul
  2019-03-29 15:36         ` Daniel Vetter
  0 siblings, 1 reply; 30+ messages in thread
From: Sean Paul @ 2019-03-29 13:16 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul, dri-devel, Sean Paul

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

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-03-29 13:16       ` Sean Paul
@ 2019-03-29 15:36         ` Daniel Vetter
  2019-03-29 18:10           ` Sean Paul
  0 siblings, 1 reply; 30+ messages in thread
From: Daniel Vetter @ 2019-03-29 15:36 UTC (permalink / raw)
  To: Sean Paul
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul, dri-devel

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
-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-03-29 15:36         ` Daniel Vetter
@ 2019-03-29 18:10           ` Sean Paul
  2019-03-29 19:21             ` Daniel Vetter
  0 siblings, 1 reply; 30+ messages in thread
From: Sean Paul @ 2019-03-29 18:10 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul, dri-devel, Sean Paul

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

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 3/5] drm/rockchip: Use the helpers for PSR
  2019-03-26 20:44 ` [PATCH v2 3/5] drm/rockchip: Use the helpers for PSR Sean Paul
@ 2019-03-29 18:51   ` Heiko Stübner
  2019-03-29 19:00     ` Sean Paul
  0 siblings, 1 reply; 30+ messages in thread
From: Heiko Stübner @ 2019-03-29 18:51 UTC (permalink / raw)
  To: Sean Paul
  Cc: Zain Wang, David Airlie, dri-devel, Tomasz Figa, Sean Paul,
	Laurent Pinchart

Hi,

Am Dienstag, 26. März 2019, 21:44:56 CET schrieb Sean Paul:
> From: Sean Paul <seanpaul@chromium.org>
> 
> Instead of rolling our own implementation for tracking when PSR should
> be [in]active, use the new self refresh helpers to do the heavy lifting.

I only got patches 3-5 and had to pull 1+2 from patchwork, the following
applies to the whole series though.

While my Kevin display does still generally work with these 5 patches
applied functionality has regressed somehow.

Environment is a standard Debian with framebuffer console and sddm
login manager.

With this patchset applied, when sddm starts I only get the see the
login screen after I move the mouse, similarly the blinking cursor in
the password field also only blinks when I actively move the cursor
and stops when there is no mouse movement.

With all 5 patches reverted this goes back to normal.

So it looks like the PSR gets a bit too agressive with these?


Heiko

> Changes in v2:
> - updated to reflect changes made in the helpers
> 
> Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-4-sean@poorly.run
> 
> Cc: Zain Wang <wzz@rock-chips.com>
> Cc: Tomasz Figa <tfiga@chromium.org>
> Signed-off-by: Sean Paul <seanpaul@chromium.org>
> ---
>  .../drm/bridge/analogix/analogix_dp_core.c    | 213 ++++++++-----
>  .../drm/bridge/analogix/analogix_dp_core.h    |   2 +-
>  drivers/gpu/drm/rockchip/Makefile             |   3 +-
>  .../gpu/drm/rockchip/analogix_dp-rockchip.c   |  70 ++---
>  drivers/gpu/drm/rockchip/rockchip_drm_fb.c    |  16 -
>  drivers/gpu/drm/rockchip/rockchip_drm_psr.c   | 290 ------------------
>  drivers/gpu/drm/rockchip/rockchip_drm_psr.h   |  30 --
>  drivers/gpu/drm/rockchip/rockchip_drm_vop.c   |  33 +-
>  include/drm/bridge/analogix_dp.h              |   4 +-
>  9 files changed, 185 insertions(+), 476 deletions(-)
>  delete mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_psr.c
>  delete mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_psr.h
> 
> diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
> index af34554a5a02..c503fe3bc3b3 100644
> --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
> +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
> @@ -106,63 +106,13 @@ static int analogix_dp_detect_hpd(struct analogix_dp_device *dp)
>  	return 0;
>  }
>  
> -int analogix_dp_psr_enabled(struct analogix_dp_device *dp)
> +bool analogix_dp_self_refresh_changed(struct analogix_dp_device *dp)
>  {
> -
> -	return dp->psr_enable;
> +	return dp->connector.state->self_refresh_changed;
>  }
> -EXPORT_SYMBOL_GPL(analogix_dp_psr_enabled);
> +EXPORT_SYMBOL_GPL(analogix_dp_self_refresh_changed);
>  
> -int analogix_dp_enable_psr(struct analogix_dp_device *dp)
> -{
> -	struct edp_vsc_psr psr_vsc;
> -
> -	if (!dp->psr_enable)
> -		return 0;
> -
> -	/* Prepare VSC packet as per EDP 1.4 spec, Table 6.9 */
> -	memset(&psr_vsc, 0, sizeof(psr_vsc));
> -	psr_vsc.sdp_header.HB0 = 0;
> -	psr_vsc.sdp_header.HB1 = 0x7;
> -	psr_vsc.sdp_header.HB2 = 0x2;
> -	psr_vsc.sdp_header.HB3 = 0x8;
> -
> -	psr_vsc.DB0 = 0;
> -	psr_vsc.DB1 = EDP_VSC_PSR_STATE_ACTIVE | EDP_VSC_PSR_CRC_VALUES_VALID;
> -
> -	return analogix_dp_send_psr_spd(dp, &psr_vsc, true);
> -}
> -EXPORT_SYMBOL_GPL(analogix_dp_enable_psr);
> -
> -int analogix_dp_disable_psr(struct analogix_dp_device *dp)
> -{
> -	struct edp_vsc_psr psr_vsc;
> -	int ret;
> -
> -	if (!dp->psr_enable)
> -		return 0;
> -
> -	/* Prepare VSC packet as per EDP 1.4 spec, Table 6.9 */
> -	memset(&psr_vsc, 0, sizeof(psr_vsc));
> -	psr_vsc.sdp_header.HB0 = 0;
> -	psr_vsc.sdp_header.HB1 = 0x7;
> -	psr_vsc.sdp_header.HB2 = 0x2;
> -	psr_vsc.sdp_header.HB3 = 0x8;
> -
> -	psr_vsc.DB0 = 0;
> -	psr_vsc.DB1 = 0;
> -
> -	ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, DP_SET_POWER_D0);
> -	if (ret != 1) {
> -		dev_err(dp->dev, "Failed to set DP Power0 %d\n", ret);
> -		return ret;
> -	}
> -
> -	return analogix_dp_send_psr_spd(dp, &psr_vsc, false);
> -}
> -EXPORT_SYMBOL_GPL(analogix_dp_disable_psr);
> -
> -static int analogix_dp_detect_sink_psr(struct analogix_dp_device *dp)
> +static bool analogix_dp_detect_sink_psr(struct analogix_dp_device *dp)
>  {
>  	unsigned char psr_version;
>  	int ret;
> @@ -170,14 +120,11 @@ static int analogix_dp_detect_sink_psr(struct analogix_dp_device *dp)
>  	ret = drm_dp_dpcd_readb(&dp->aux, DP_PSR_SUPPORT, &psr_version);
>  	if (ret != 1) {
>  		dev_err(dp->dev, "failed to get PSR version, disable it\n");
> -		return ret;
> +		return false;
>  	}
>  
>  	dev_dbg(dp->dev, "Panel PSR version : %x\n", psr_version);
> -
> -	dp->psr_enable = (psr_version & DP_PSR_IS_SUPPORTED) ? true : false;
> -
> -	return 0;
> +	return psr_version & DP_PSR_IS_SUPPORTED;
>  }
>  
>  static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp)
> @@ -200,7 +147,7 @@ static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp)
>  	}
>  
>  	/* Main-Link transmitter remains active during PSR active states */
> -	psr_en = DP_PSR_MAIN_LINK_ACTIVE | DP_PSR_CRC_VERIFICATION;
> +	psr_en = DP_PSR_CRC_VERIFICATION;
>  	ret = drm_dp_dpcd_writeb(&dp->aux, DP_PSR_EN_CFG, psr_en);
>  	if (ret != 1) {
>  		dev_err(dp->dev, "failed to set panel psr\n");
> @@ -208,8 +155,7 @@ static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp)
>  	}
>  
>  	/* Enable psr function */
> -	psr_en = DP_PSR_ENABLE | DP_PSR_MAIN_LINK_ACTIVE |
> -		 DP_PSR_CRC_VERIFICATION;
> +	psr_en = DP_PSR_ENABLE | DP_PSR_CRC_VERIFICATION;
>  	ret = drm_dp_dpcd_writeb(&dp->aux, DP_PSR_EN_CFG, psr_en);
>  	if (ret != 1) {
>  		dev_err(dp->dev, "failed to set panel psr\n");
> @@ -218,10 +164,11 @@ static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp)
>  
>  	analogix_dp_enable_psr_crc(dp);
>  
> +	dp->psr_supported = true;
> +
>  	return 0;
>  end:
>  	dev_err(dp->dev, "enable psr fail, force to disable psr\n");
> -	dp->psr_enable = false;
>  
>  	return ret;
>  }
> @@ -1036,25 +983,90 @@ static int analogix_dp_commit(struct analogix_dp_device *dp)
>  		}
>  	}
>  
> -	ret = analogix_dp_detect_sink_psr(dp);
> -	if (ret)
> -		return ret;
> -
>  	/* Check whether panel supports fast training */
>  	ret = analogix_dp_fast_link_train_detection(dp);
>  	if (ret)
> -		dp->psr_enable = false;
> +		return ret;
>  
> -	if (dp->psr_enable) {
> +	if (analogix_dp_detect_sink_psr(dp)) {
>  		ret = analogix_dp_enable_sink_psr(dp);
>  		if (ret)
>  			return ret;
>  	}
>  
> +	return ret;
> +}
> +
> +static int analogix_dp_enable_psr(struct analogix_dp_device *dp)
> +{
> +	struct edp_vsc_psr psr_vsc;
> +	int ret;
> +	u8 sink;
> +
> +	ret = drm_dp_dpcd_readb(&dp->aux, DP_PSR_STATUS, &sink);
> +	if (ret != 1)
> +		DRM_DEV_ERROR(dp->dev, "Failed to read psr status %d\n", ret);
> +	else if (sink == DP_PSR_SINK_ACTIVE_RFB)
> +		return 0;
> +
> +	/* Prepare VSC packet as per EDP 1.4 spec, Table 6.9 */
> +	memset(&psr_vsc, 0, sizeof(psr_vsc));
> +	psr_vsc.sdp_header.HB0 = 0;
> +	psr_vsc.sdp_header.HB1 = 0x7;
> +	psr_vsc.sdp_header.HB2 = 0x2;
> +	psr_vsc.sdp_header.HB3 = 0x8;
> +	psr_vsc.DB0 = 0;
> +	psr_vsc.DB1 = EDP_VSC_PSR_STATE_ACTIVE | EDP_VSC_PSR_CRC_VALUES_VALID;
> +
> +	ret = analogix_dp_send_psr_spd(dp, &psr_vsc, true);
> +	if (!ret)
> +		analogix_dp_set_analog_power_down(dp, POWER_ALL, true);
>  
>  	return ret;
>  }
>  
> +static int analogix_dp_disable_psr(struct analogix_dp_device *dp)
> +{
> +	struct edp_vsc_psr psr_vsc;
> +	int ret;
> +	u8 sink;
> +
> +	analogix_dp_set_analog_power_down(dp, POWER_ALL, false);
> +
> +	ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, DP_SET_POWER_D0);
> +	if (ret != 1) {
> +		DRM_DEV_ERROR(dp->dev, "Failed to set DP Power0 %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = drm_dp_dpcd_readb(&dp->aux, DP_PSR_STATUS, &sink);
> +	if (ret != 1) {
> +		DRM_DEV_ERROR(dp->dev, "Failed to read psr status %d\n", ret);
> +		return ret;
> +	} else if (sink == DP_PSR_SINK_INACTIVE) {
> +		DRM_DEV_ERROR(dp->dev, "sink inactive, skip disable psr");
> +		return 0;
> +	}
> +
> +	ret = analogix_dp_train_link(dp);
> +	if (ret) {
> +		DRM_DEV_ERROR(dp->dev, "Failed to train the link %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* Prepare VSC packet as per EDP 1.4 spec, Table 6.9 */
> +	memset(&psr_vsc, 0, sizeof(psr_vsc));
> +	psr_vsc.sdp_header.HB0 = 0;
> +	psr_vsc.sdp_header.HB1 = 0x7;
> +	psr_vsc.sdp_header.HB2 = 0x2;
> +	psr_vsc.sdp_header.HB3 = 0x8;
> +
> +	psr_vsc.DB0 = 0;
> +	psr_vsc.DB1 = 0;
> +
> +	return analogix_dp_send_psr_spd(dp, &psr_vsc, true);
> +}
> +
>  /*
>   * This function is a bit of a catch-all for panel preparation, hopefully
>   * simplifying the logic of functions that need to prepare/unprepare the panel
> @@ -1145,9 +1157,23 @@ analogix_dp_best_encoder(struct drm_connector *connector)
>  	return dp->encoder;
>  }
>  
> +
> +int analogix_dp_atomic_check(struct drm_connector *connector,
> +			     struct drm_connector_state *state)
> +{
> +	struct analogix_dp_device *dp = to_dp(connector);
> +
> +	state->self_refresh_aware = true;
> +	if (state->self_refresh_active && !dp->psr_supported)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
>  static const struct drm_connector_helper_funcs analogix_dp_connector_helper_funcs = {
>  	.get_modes = analogix_dp_get_modes,
>  	.best_encoder = analogix_dp_best_encoder,
> +	.atomic_check = analogix_dp_atomic_check,
>  };
>  
>  static enum drm_connector_status
> @@ -1244,6 +1270,10 @@ static void analogix_dp_bridge_pre_enable(struct drm_bridge *bridge)
>  	struct analogix_dp_device *dp = bridge->driver_private;
>  	int ret;
>  
> +	/* Don't touch the panel if we're coming back from PSR */
> +	if (dp->connector.state->self_refresh_changed)
> +		return;
> +
>  	ret = analogix_dp_prepare_panel(dp, true, true);
>  	if (ret)
>  		DRM_ERROR("failed to setup the panel ret = %d\n", ret);
> @@ -1309,6 +1339,14 @@ static void analogix_dp_bridge_enable(struct drm_bridge *bridge)
>  	struct analogix_dp_device *dp = bridge->driver_private;
>  	int timeout_loop = 0;
>  
> +	/* Not a full enable, just disable PSR and continue */
> +	if (dp->connector.state->self_refresh_changed) {
> +		int ret = analogix_dp_disable_psr(dp);
> +		if (ret)
> +			DRM_ERROR("Failed to disable psr %d\n", ret);
> +		return;
> +	}
> +
>  	if (dp->dpms_mode == DRM_MODE_DPMS_ON)
>  		return;
>  
> @@ -1356,11 +1394,37 @@ static void analogix_dp_bridge_disable(struct drm_bridge *bridge)
>  	if (ret)
>  		DRM_ERROR("failed to setup the panel ret = %d\n", ret);
>  
> -	dp->psr_enable = false;
>  	dp->fast_train_enable = false;
> +	dp->psr_supported = false;
>  	dp->dpms_mode = DRM_MODE_DPMS_OFF;
>  }
>  
> +static void analogix_dp_bridge_atomic_disable(struct drm_bridge *bridge)
> +{
> +	struct analogix_dp_device *dp = bridge->driver_private;
> +	struct drm_connector_state *conn_state = dp->connector.state;
> +
> +	/* Don't do a full disable on PSR transitions */
> +	if (conn_state->crtc && conn_state->self_refresh_changed)
> +		return;
> +
> +	analogix_dp_bridge_disable(bridge);
> +}
> +
> +static void analogix_dp_bridge_post_disable(struct drm_bridge *bridge)
> +{
> +	struct analogix_dp_device *dp = bridge->driver_private;
> +	struct drm_connector_state *conn_state = dp->connector.state;
> +	int ret;
> +
> +	if (conn_state->self_refresh_active) {
> +		ret = analogix_dp_enable_psr(dp);
> +		if (ret)
> +			DRM_ERROR("Failed to enable psr (%d)\n", ret);
> +		return;
> +	}
> +}
> +
>  static void analogix_dp_bridge_mode_set(struct drm_bridge *bridge,
>  				const struct drm_display_mode *orig_mode,
>  				const struct drm_display_mode *mode)
> @@ -1440,16 +1504,11 @@ static void analogix_dp_bridge_mode_set(struct drm_bridge *bridge,
>  		video->interlaced = true;
>  }
>  
> -static void analogix_dp_bridge_nop(struct drm_bridge *bridge)
> -{
> -	/* do nothing */
> -}
> -
>  static const struct drm_bridge_funcs analogix_dp_bridge_funcs = {
>  	.pre_enable = analogix_dp_bridge_pre_enable,
>  	.enable = analogix_dp_bridge_enable,
> -	.disable = analogix_dp_bridge_disable,
> -	.post_disable = analogix_dp_bridge_nop,
> +	.disable = analogix_dp_bridge_atomic_disable,
> +	.post_disable = analogix_dp_bridge_post_disable,
>  	.mode_set = analogix_dp_bridge_mode_set,
>  	.attach = analogix_dp_bridge_attach,
>  };
> diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h
> index 769255dc6e99..d937fa3d9ee8 100644
> --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h
> +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h
> @@ -173,8 +173,8 @@ struct analogix_dp_device {
>  	int			dpms_mode;
>  	int			hpd_gpio;
>  	bool                    force_hpd;
> -	bool			psr_enable;
>  	bool			fast_train_enable;
> +	bool			psr_supported;
>  
>  	struct mutex		panel_lock;
>  	bool			panel_is_modeset;
> diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
> index f6fc9d5dd0ad..a777726c23cb 100644
> --- a/drivers/gpu/drm/rockchip/Makefile
> +++ b/drivers/gpu/drm/rockchip/Makefile
> @@ -4,8 +4,7 @@
>  # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
>  
>  rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
> -		rockchip_drm_gem.o rockchip_drm_psr.o \
> -		rockchip_drm_vop.o rockchip_vop_reg.o
> +		rockchip_drm_gem.o rockchip_drm_vop.o rockchip_vop_reg.o
>  rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o
>  
>  rockchipdrm-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o
> diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
> index bc4423624209..8f42c38848db 100644
> --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
> +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
> @@ -32,7 +32,6 @@
>  #include <drm/bridge/analogix_dp.h>
>  
>  #include "rockchip_drm_drv.h"
> -#include "rockchip_drm_psr.h"
>  #include "rockchip_drm_vop.h"
>  
>  #define RK3288_GRF_SOC_CON6		0x25c
> @@ -77,29 +76,6 @@ struct rockchip_dp_device {
>  	struct analogix_dp_plat_data plat_data;
>  };
>  
> -static int analogix_dp_psr_set(struct drm_encoder *encoder, bool enabled)
> -{
> -	struct rockchip_dp_device *dp = to_dp(encoder);
> -	int ret;
> -
> -	if (!analogix_dp_psr_enabled(dp->adp))
> -		return 0;
> -
> -	DRM_DEV_DEBUG(dp->dev, "%s PSR...\n", enabled ? "Entry" : "Exit");
> -
> -	ret = rockchip_drm_wait_vact_end(dp->encoder.crtc,
> -					 PSR_WAIT_LINE_FLAG_TIMEOUT_MS);
> -	if (ret) {
> -		DRM_DEV_ERROR(dp->dev, "line flag interrupt did not arrive\n");
> -		return -ETIMEDOUT;
> -	}
> -
> -	if (enabled)
> -		return analogix_dp_enable_psr(dp->adp);
> -	else
> -		return analogix_dp_disable_psr(dp->adp);
> -}
> -
>  static int rockchip_dp_pre_init(struct rockchip_dp_device *dp)
>  {
>  	reset_control_assert(dp->rst);
> @@ -130,21 +106,9 @@ static int rockchip_dp_poweron_start(struct analogix_dp_plat_data *plat_data)
>  	return ret;
>  }
>  
> -static int rockchip_dp_poweron_end(struct analogix_dp_plat_data *plat_data)
> -{
> -	struct rockchip_dp_device *dp = to_dp(plat_data);
> -
> -	return rockchip_drm_psr_inhibit_put(&dp->encoder);
> -}
> -
>  static int rockchip_dp_powerdown(struct analogix_dp_plat_data *plat_data)
>  {
>  	struct rockchip_dp_device *dp = to_dp(plat_data);
> -	int ret;
> -
> -	ret = rockchip_drm_psr_inhibit_get(&dp->encoder);
> -	if (ret != 0)
> -		return ret;
>  
>  	clk_disable_unprepare(dp->pclk);
>  
> @@ -190,6 +154,9 @@ static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder)
>  	int ret;
>  	u32 val;
>  
> +	if (analogix_dp_self_refresh_changed(dp->adp))
> +		return;
> +
>  	ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, encoder);
>  	if (ret < 0)
>  		return;
> @@ -214,9 +181,24 @@ static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder)
>  	clk_disable_unprepare(dp->grfclk);
>  }
>  
> -static void rockchip_dp_drm_encoder_nop(struct drm_encoder *encoder)
> +static void rockchip_dp_drm_encoder_disable(struct drm_encoder *encoder)
>  {
> -	/* do nothing */
> +	struct rockchip_dp_device *dp = to_dp(encoder);
> +	struct drm_crtc *crtc;
> +	int ret;
> +
> +	drm_for_each_crtc(crtc, dp->drm_dev) {
> +		if (!(crtc->state->encoder_mask & drm_encoder_mask(encoder)))
> +			continue;
> +
> +		if (!crtc->state->self_refresh_active)
> +			continue;
> +
> +		ret = rockchip_drm_wait_vact_end(crtc,
> +						 PSR_WAIT_LINE_FLAG_TIMEOUT_MS);
> +		if (ret)
> +			DRM_DEV_ERROR(dp->dev, "line flag irq timed out\n");
> +	}
>  }
>  
>  static int
> @@ -246,7 +228,7 @@ static struct drm_encoder_helper_funcs rockchip_dp_encoder_helper_funcs = {
>  	.mode_fixup = rockchip_dp_drm_encoder_mode_fixup,
>  	.mode_set = rockchip_dp_drm_encoder_mode_set,
>  	.enable = rockchip_dp_drm_encoder_enable,
> -	.disable = rockchip_dp_drm_encoder_nop,
> +	.disable = rockchip_dp_drm_encoder_disable,
>  	.atomic_check = rockchip_dp_drm_encoder_atomic_check,
>  };
>  
> @@ -338,23 +320,16 @@ static int rockchip_dp_bind(struct device *dev, struct device *master,
>  
>  	dp->plat_data.dev_type = dp->data->chip_type;
>  	dp->plat_data.power_on_start = rockchip_dp_poweron_start;
> -	dp->plat_data.power_on_end = rockchip_dp_poweron_end;
>  	dp->plat_data.power_off = rockchip_dp_powerdown;
>  	dp->plat_data.get_modes = rockchip_dp_get_modes;
>  
> -	ret = rockchip_drm_psr_register(&dp->encoder, analogix_dp_psr_set);
> -	if (ret < 0)
> -		goto err_cleanup_encoder;
> -
>  	dp->adp = analogix_dp_bind(dev, dp->drm_dev, &dp->plat_data);
>  	if (IS_ERR(dp->adp)) {
>  		ret = PTR_ERR(dp->adp);
> -		goto err_unreg_psr;
> +		goto err_cleanup_encoder;
>  	}
>  
>  	return 0;
> -err_unreg_psr:
> -	rockchip_drm_psr_unregister(&dp->encoder);
>  err_cleanup_encoder:
>  	dp->encoder.funcs->destroy(&dp->encoder);
>  	return ret;
> @@ -366,7 +341,6 @@ static void rockchip_dp_unbind(struct device *dev, struct device *master,
>  	struct rockchip_dp_device *dp = dev_get_drvdata(dev);
>  
>  	analogix_dp_unbind(dp->adp);
> -	rockchip_drm_psr_unregister(&dp->encoder);
>  	dp->encoder.funcs->destroy(&dp->encoder);
>  
>  	dp->adp = ERR_PTR(-ENODEV);
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
> index 97438bbbe389..7e121875e3c9 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
> @@ -23,22 +23,10 @@
>  #include "rockchip_drm_drv.h"
>  #include "rockchip_drm_fb.h"
>  #include "rockchip_drm_gem.h"
> -#include "rockchip_drm_psr.h"
> -
> -static int rockchip_drm_fb_dirty(struct drm_framebuffer *fb,
> -				 struct drm_file *file,
> -				 unsigned int flags, unsigned int color,
> -				 struct drm_clip_rect *clips,
> -				 unsigned int num_clips)
> -{
> -	rockchip_drm_psr_flush_all(fb->dev);
> -	return 0;
> -}
>  
>  static const struct drm_framebuffer_funcs rockchip_drm_fb_funcs = {
>  	.destroy       = drm_gem_fb_destroy,
>  	.create_handle = drm_gem_fb_create_handle,
> -	.dirty	       = rockchip_drm_fb_dirty,
>  };
>  
>  static struct drm_framebuffer *
> @@ -132,8 +120,6 @@ rockchip_atomic_helper_commit_tail_rpm(struct drm_atomic_state *old_state)
>  {
>  	struct drm_device *dev = old_state->dev;
>  
> -	rockchip_drm_psr_inhibit_get_state(old_state);
> -
>  	drm_atomic_helper_commit_modeset_disables(dev, old_state);
>  
>  	drm_atomic_helper_commit_modeset_enables(dev, old_state);
> @@ -141,8 +127,6 @@ rockchip_atomic_helper_commit_tail_rpm(struct drm_atomic_state *old_state)
>  	drm_atomic_helper_commit_planes(dev, old_state,
>  					DRM_PLANE_COMMIT_ACTIVE_ONLY);
>  
> -	rockchip_drm_psr_inhibit_put_state(old_state);
> -
>  	drm_atomic_helper_commit_hw_done(old_state);
>  
>  	drm_atomic_helper_wait_for_vblanks(dev, old_state);
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_psr.c b/drivers/gpu/drm/rockchip/rockchip_drm_psr.c
> deleted file mode 100644
> index a0c8bd235b67..000000000000
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_psr.c
> +++ /dev/null
> @@ -1,290 +0,0 @@
> -/*
> - * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> - * Author: Yakir Yang <ykk@rock-chips.com>
> - *
> - * This software is licensed under the terms of the GNU General Public
> - * License version 2, as published by the Free Software Foundation, and
> - * may be copied, distributed, and modified under those terms.
> - *
> - * This program is distributed in the hope that it will be useful,
> - * but WITHOUT ANY WARRANTY; without even the implied warranty of
> - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> - * GNU General Public License for more details.
> - */
> -
> -#include <drm/drmP.h>
> -#include <drm/drm_atomic.h>
> -#include <drm/drm_probe_helper.h>
> -
> -#include "rockchip_drm_drv.h"
> -#include "rockchip_drm_psr.h"
> -
> -#define PSR_FLUSH_TIMEOUT_MS	100
> -
> -struct psr_drv {
> -	struct list_head	list;
> -	struct drm_encoder	*encoder;
> -
> -	struct mutex		lock;
> -	int			inhibit_count;
> -	bool			enabled;
> -
> -	struct delayed_work	flush_work;
> -
> -	int (*set)(struct drm_encoder *encoder, bool enable);
> -};
> -
> -static struct psr_drv *find_psr_by_encoder(struct drm_encoder *encoder)
> -{
> -	struct rockchip_drm_private *drm_drv = encoder->dev->dev_private;
> -	struct psr_drv *psr;
> -
> -	mutex_lock(&drm_drv->psr_list_lock);
> -	list_for_each_entry(psr, &drm_drv->psr_list, list) {
> -		if (psr->encoder == encoder)
> -			goto out;
> -	}
> -	psr = ERR_PTR(-ENODEV);
> -
> -out:
> -	mutex_unlock(&drm_drv->psr_list_lock);
> -	return psr;
> -}
> -
> -static int psr_set_state_locked(struct psr_drv *psr, bool enable)
> -{
> -	int ret;
> -
> -	if (psr->inhibit_count > 0)
> -		return -EINVAL;
> -
> -	if (enable == psr->enabled)
> -		return 0;
> -
> -	ret = psr->set(psr->encoder, enable);
> -	if (ret)
> -		return ret;
> -
> -	psr->enabled = enable;
> -	return 0;
> -}
> -
> -static void psr_flush_handler(struct work_struct *work)
> -{
> -	struct psr_drv *psr = container_of(to_delayed_work(work),
> -					   struct psr_drv, flush_work);
> -
> -	mutex_lock(&psr->lock);
> -	psr_set_state_locked(psr, true);
> -	mutex_unlock(&psr->lock);
> -}
> -
> -/**
> - * rockchip_drm_psr_inhibit_put - release PSR inhibit on given encoder
> - * @encoder: encoder to obtain the PSR encoder
> - *
> - * Decrements PSR inhibit count on given encoder. Should be called only
> - * for a PSR inhibit count increment done before. If PSR inhibit counter
> - * reaches zero, PSR flush work is scheduled to make the hardware enter
> - * PSR mode in PSR_FLUSH_TIMEOUT_MS.
> - *
> - * Returns:
> - * Zero on success, negative errno on failure.
> - */
> -int rockchip_drm_psr_inhibit_put(struct drm_encoder *encoder)
> -{
> -	struct psr_drv *psr = find_psr_by_encoder(encoder);
> -
> -	if (IS_ERR(psr))
> -		return PTR_ERR(psr);
> -
> -	mutex_lock(&psr->lock);
> -	--psr->inhibit_count;
> -	WARN_ON(psr->inhibit_count < 0);
> -	if (!psr->inhibit_count)
> -		mod_delayed_work(system_wq, &psr->flush_work,
> -				 PSR_FLUSH_TIMEOUT_MS);
> -	mutex_unlock(&psr->lock);
> -
> -	return 0;
> -}
> -EXPORT_SYMBOL(rockchip_drm_psr_inhibit_put);
> -
> -void rockchip_drm_psr_inhibit_get_state(struct drm_atomic_state *state)
> -{
> -	struct drm_crtc *crtc;
> -	struct drm_crtc_state *crtc_state;
> -	struct drm_encoder *encoder;
> -	u32 encoder_mask = 0;
> -	int i;
> -
> -	for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> -		encoder_mask |= crtc_state->encoder_mask;
> -		encoder_mask |= crtc->state->encoder_mask;
> -	}
> -
> -	drm_for_each_encoder_mask(encoder, state->dev, encoder_mask)
> -		rockchip_drm_psr_inhibit_get(encoder);
> -}
> -EXPORT_SYMBOL(rockchip_drm_psr_inhibit_get_state);
> -
> -void rockchip_drm_psr_inhibit_put_state(struct drm_atomic_state *state)
> -{
> -	struct drm_crtc *crtc;
> -	struct drm_crtc_state *crtc_state;
> -	struct drm_encoder *encoder;
> -	u32 encoder_mask = 0;
> -	int i;
> -
> -	for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> -		encoder_mask |= crtc_state->encoder_mask;
> -		encoder_mask |= crtc->state->encoder_mask;
> -	}
> -
> -	drm_for_each_encoder_mask(encoder, state->dev, encoder_mask)
> -		rockchip_drm_psr_inhibit_put(encoder);
> -}
> -EXPORT_SYMBOL(rockchip_drm_psr_inhibit_put_state);
> -
> -/**
> - * rockchip_drm_psr_inhibit_get - acquire PSR inhibit on given encoder
> - * @encoder: encoder to obtain the PSR encoder
> - *
> - * Increments PSR inhibit count on given encoder. This function guarantees
> - * that after it returns PSR is turned off on given encoder and no PSR-related
> - * hardware state change occurs at least until a matching call to
> - * rockchip_drm_psr_inhibit_put() is done.
> - *
> - * Returns:
> - * Zero on success, negative errno on failure.
> - */
> -int rockchip_drm_psr_inhibit_get(struct drm_encoder *encoder)
> -{
> -	struct psr_drv *psr = find_psr_by_encoder(encoder);
> -
> -	if (IS_ERR(psr))
> -		return PTR_ERR(psr);
> -
> -	mutex_lock(&psr->lock);
> -	psr_set_state_locked(psr, false);
> -	++psr->inhibit_count;
> -	mutex_unlock(&psr->lock);
> -	cancel_delayed_work_sync(&psr->flush_work);
> -
> -	return 0;
> -}
> -EXPORT_SYMBOL(rockchip_drm_psr_inhibit_get);
> -
> -static void rockchip_drm_do_flush(struct psr_drv *psr)
> -{
> -	cancel_delayed_work_sync(&psr->flush_work);
> -
> -	mutex_lock(&psr->lock);
> -	if (!psr_set_state_locked(psr, false))
> -		mod_delayed_work(system_wq, &psr->flush_work,
> -				 PSR_FLUSH_TIMEOUT_MS);
> -	mutex_unlock(&psr->lock);
> -}
> -
> -/**
> - * rockchip_drm_psr_flush_all - force to flush all registered PSR encoders
> - * @dev: drm device
> - *
> - * Disable the PSR function for all registered encoders, and then enable the
> - * PSR function back after PSR_FLUSH_TIMEOUT. If encoder PSR state have been
> - * changed during flush time, then keep the state no change after flush
> - * timeout.
> - *
> - * Returns:
> - * Zero on success, negative errno on failure.
> - */
> -void rockchip_drm_psr_flush_all(struct drm_device *dev)
> -{
> -	struct rockchip_drm_private *drm_drv = dev->dev_private;
> -	struct psr_drv *psr;
> -
> -	mutex_lock(&drm_drv->psr_list_lock);
> -	list_for_each_entry(psr, &drm_drv->psr_list, list)
> -		rockchip_drm_do_flush(psr);
> -	mutex_unlock(&drm_drv->psr_list_lock);
> -}
> -EXPORT_SYMBOL(rockchip_drm_psr_flush_all);
> -
> -/**
> - * rockchip_drm_psr_register - register encoder to psr driver
> - * @encoder: encoder that obtain the PSR function
> - * @psr_set: call back to set PSR state
> - *
> - * The function returns with PSR inhibit counter initialized with one
> - * and the caller (typically encoder driver) needs to call
> - * rockchip_drm_psr_inhibit_put() when it becomes ready to accept PSR
> - * enable request.
> - *
> - * Returns:
> - * Zero on success, negative errno on failure.
> - */
> -int rockchip_drm_psr_register(struct drm_encoder *encoder,
> -			int (*psr_set)(struct drm_encoder *, bool enable))
> -{
> -	struct rockchip_drm_private *drm_drv;
> -	struct psr_drv *psr;
> -
> -	if (!encoder || !psr_set)
> -		return -EINVAL;
> -
> -	drm_drv = encoder->dev->dev_private;
> -
> -	psr = kzalloc(sizeof(struct psr_drv), GFP_KERNEL);
> -	if (!psr)
> -		return -ENOMEM;
> -
> -	INIT_DELAYED_WORK(&psr->flush_work, psr_flush_handler);
> -	mutex_init(&psr->lock);
> -
> -	psr->inhibit_count = 1;
> -	psr->enabled = false;
> -	psr->encoder = encoder;
> -	psr->set = psr_set;
> -
> -	mutex_lock(&drm_drv->psr_list_lock);
> -	list_add_tail(&psr->list, &drm_drv->psr_list);
> -	mutex_unlock(&drm_drv->psr_list_lock);
> -
> -	return 0;
> -}
> -EXPORT_SYMBOL(rockchip_drm_psr_register);
> -
> -/**
> - * rockchip_drm_psr_unregister - unregister encoder to psr driver
> - * @encoder: encoder that obtain the PSR function
> - * @psr_set: call back to set PSR state
> - *
> - * It is expected that the PSR inhibit counter is 1 when this function is
> - * called, which corresponds to a state when related encoder has been
> - * disconnected from any CRTCs and its driver called
> - * rockchip_drm_psr_inhibit_get() to stop the PSR logic.
> - *
> - * Returns:
> - * Zero on success, negative errno on failure.
> - */
> -void rockchip_drm_psr_unregister(struct drm_encoder *encoder)
> -{
> -	struct rockchip_drm_private *drm_drv = encoder->dev->dev_private;
> -	struct psr_drv *psr, *n;
> -
> -	mutex_lock(&drm_drv->psr_list_lock);
> -	list_for_each_entry_safe(psr, n, &drm_drv->psr_list, list) {
> -		if (psr->encoder == encoder) {
> -			/*
> -			 * Any other value would mean that the encoder
> -			 * is still in use.
> -			 */
> -			WARN_ON(psr->inhibit_count != 1);
> -
> -			list_del(&psr->list);
> -			kfree(psr);
> -		}
> -	}
> -	mutex_unlock(&drm_drv->psr_list_lock);
> -}
> -EXPORT_SYMBOL(rockchip_drm_psr_unregister);
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_psr.h b/drivers/gpu/drm/rockchip/rockchip_drm_psr.h
> deleted file mode 100644
> index 25350ba3237b..000000000000
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_psr.h
> +++ /dev/null
> @@ -1,30 +0,0 @@
> -/*
> - * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> - * Author: Yakir Yang <ykk@rock-chips.com>
> - *
> - * This software is licensed under the terms of the GNU General Public
> - * License version 2, as published by the Free Software Foundation, and
> - * may be copied, distributed, and modified under those terms.
> - *
> - * This program is distributed in the hope that it will be useful,
> - * but WITHOUT ANY WARRANTY; without even the implied warranty of
> - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> - * GNU General Public License for more details.
> - */
> -
> -#ifndef __ROCKCHIP_DRM_PSR___
> -#define __ROCKCHIP_DRM_PSR___
> -
> -void rockchip_drm_psr_flush_all(struct drm_device *dev);
> -
> -int rockchip_drm_psr_inhibit_put(struct drm_encoder *encoder);
> -int rockchip_drm_psr_inhibit_get(struct drm_encoder *encoder);
> -
> -void rockchip_drm_psr_inhibit_get_state(struct drm_atomic_state *state);
> -void rockchip_drm_psr_inhibit_put_state(struct drm_atomic_state *state);
> -
> -int rockchip_drm_psr_register(struct drm_encoder *encoder,
> -			int (*psr_set)(struct drm_encoder *, bool enable));
> -void rockchip_drm_psr_unregister(struct drm_encoder *encoder);
> -
> -#endif /* __ROCKCHIP_DRM_PSR__ */
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> index c7d4c6073ea5..21fccda70356 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> @@ -21,6 +21,7 @@
>  #include <drm/drm_gem_framebuffer_helper.h>
>  #include <drm/drm_plane_helper.h>
>  #include <drm/drm_probe_helper.h>
> +#include <drm/drm_self_refresh_helper.h>
>  #ifdef CONFIG_DRM_ANALOGIX_DP
>  #include <drm/bridge/analogix_dp.h>
>  #endif
> @@ -42,10 +43,11 @@
>  #include "rockchip_drm_drv.h"
>  #include "rockchip_drm_gem.h"
>  #include "rockchip_drm_fb.h"
> -#include "rockchip_drm_psr.h"
>  #include "rockchip_drm_vop.h"
>  #include "rockchip_rgb.h"
>  
> +#define VOP_SELF_REFRESH_ENTRY_DELAY_MS 100
> +
>  #define VOP_WIN_SET(vop, win, name, v) \
>  		vop_reg_set(vop, &win->phy->name, win->base, ~0, v, #name)
>  #define VOP_SCL_SET(vop, win, name, v) \
> @@ -541,7 +543,7 @@ static void vop_core_clks_disable(struct vop *vop)
>  	clk_disable(vop->hclk);
>  }
>  
> -static int vop_enable(struct drm_crtc *crtc)
> +static int vop_enable(struct drm_crtc *crtc, struct drm_crtc_state *old_state)
>  {
>  	struct vop *vop = to_vop(crtc);
>  	int ret, i;
> @@ -581,12 +583,18 @@ static int vop_enable(struct drm_crtc *crtc)
>  	 * We need to make sure that all windows are disabled before we
>  	 * enable the crtc. Otherwise we might try to scan from a destroyed
>  	 * buffer later.
> +	 *
> +	 * In the case of enable-after-PSR, we don't need to worry about this
> +	 * case since the buffer is guaranteed to be valid and disabling the
> +	 * window will result in screen glitches on PSR exit.
>  	 */
> -	for (i = 0; i < vop->data->win_size; i++) {
> -		struct vop_win *vop_win = &vop->win[i];
> -		const struct vop_win_data *win = vop_win->data;
> +	if (!old_state || !old_state->self_refresh_active) {
> +		for (i = 0; i < vop->data->win_size; i++) {
> +			struct vop_win *vop_win = &vop->win[i];
> +			const struct vop_win_data *win = vop_win->data;
>  
> -		VOP_WIN_SET(vop, win, enable, 0);
> +			VOP_WIN_SET(vop, win, enable, 0);
> +		}
>  	}
>  	spin_unlock(&vop->reg_lock);
>  
> @@ -937,12 +945,10 @@ static void vop_plane_atomic_async_update(struct drm_plane *plane,
>  	}
>  
>  	if (vop->is_enabled) {
> -		rockchip_drm_psr_inhibit_get_state(new_state->state);
>  		vop_plane_atomic_update(plane, plane->state);
>  		spin_lock(&vop->reg_lock);
>  		vop_cfg_done(vop);
>  		spin_unlock(&vop->reg_lock);
> -		rockchip_drm_psr_inhibit_put_state(new_state->state);
>  	}
>  
>  	plane->funcs->atomic_destroy_state(plane, plane_state);
> @@ -1035,7 +1041,7 @@ static void vop_crtc_atomic_enable(struct drm_crtc *crtc,
>  
>  	WARN_ON(vop->event);
>  
> -	ret = vop_enable(crtc);
> +	ret = vop_enable(crtc, old_state);
>  	if (ret) {
>  		mutex_unlock(&vop->vop_lock);
>  		DRM_DEV_ERROR(vop->dev, "Failed to enable vop (%d)\n", ret);
> @@ -1509,6 +1515,13 @@ static int vop_create_crtc(struct vop *vop)
>  	init_completion(&vop->line_flag_completion);
>  	crtc->port = port;
>  
> +	ret = drm_self_refresh_helper_register(crtc,
> +					       VOP_SELF_REFRESH_ENTRY_DELAY_MS);
> +	if (ret)
> +		DRM_DEV_DEBUG_KMS(vop->dev,
> +			"Failed to register %s with SR helpers %d, ignoring\n",
> +			crtc->name, ret);
> +
>  	return 0;
>  
>  err_cleanup_crtc:
> @@ -1526,6 +1539,8 @@ static void vop_destroy_crtc(struct vop *vop)
>  	struct drm_device *drm_dev = vop->drm_dev;
>  	struct drm_plane *plane, *tmp;
>  
> +	drm_self_refresh_helper_unregister(crtc);
> +
>  	of_node_put(crtc->port);
>  
>  	/*
> diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h
> index 475b706b49de..b6cc654143d7 100644
> --- a/include/drm/bridge/analogix_dp.h
> +++ b/include/drm/bridge/analogix_dp.h
> @@ -42,9 +42,7 @@ struct analogix_dp_plat_data {
>  			 struct drm_connector *);
>  };
>  
> -int analogix_dp_psr_enabled(struct analogix_dp_device *dp);
> -int analogix_dp_enable_psr(struct analogix_dp_device *dp);
> -int analogix_dp_disable_psr(struct analogix_dp_device *dp);
> +bool analogix_dp_self_refresh_changed(struct analogix_dp_device *dp);
>  
>  int analogix_dp_resume(struct analogix_dp_device *dp);
>  int analogix_dp_suspend(struct analogix_dp_device *dp);
> 




_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 3/5] drm/rockchip: Use the helpers for PSR
  2019-03-29 18:51   ` Heiko Stübner
@ 2019-03-29 19:00     ` Sean Paul
  2019-03-29 19:02       ` Heiko Stübner
  0 siblings, 1 reply; 30+ messages in thread
From: Sean Paul @ 2019-03-29 19:00 UTC (permalink / raw)
  To: Heiko Stübner
  Cc: Zain Wang, David Airlie, dri-devel, Tomasz Figa, Sean Paul,
	Laurent Pinchart, Sean Paul

On Fri, Mar 29, 2019 at 07:51:51PM +0100, Heiko Stübner wrote:
> Hi,
> 
> Am Dienstag, 26. März 2019, 21:44:56 CET schrieb Sean Paul:
> > From: Sean Paul <seanpaul@chromium.org>
> > 
> > Instead of rolling our own implementation for tracking when PSR should
> > be [in]active, use the new self refresh helpers to do the heavy lifting.
> 
> I only got patches 3-5 and had to pull 1+2 from patchwork, the following
> applies to the whole series though.
> 
> While my Kevin display does still generally work with these 5 patches
> applied functionality has regressed somehow.
> 
> Environment is a standard Debian with framebuffer console and sddm
> login manager.

Ahh, this is probably b/c fb_dirty is not triggering PSR exit. I had it
working in v1, but removed it in v2 since Daniel suggested we require the
fb_dirty helpers. I neglected to add support for the fb_dirty helpers, so fbcon
updates won't cause the screen to refresh. I'll add the helpers in v3 and make
sure I test it out with fbcon.

Thanks for reporting this!

Sean

> 
> With this patchset applied, when sddm starts I only get the see the
> login screen after I move the mouse, similarly the blinking cursor in
> the password field also only blinks when I actively move the cursor
> and stops when there is no mouse movement.
> 
> With all 5 patches reverted this goes back to normal.
> 
> So it looks like the PSR gets a bit too agressive with these?
> 
> 
> Heiko
> 
> > Changes in v2:
> > - updated to reflect changes made in the helpers
> > 
> > Link to v1: https://patchwork.freedesktop.org/patch/msgid/20190228210939.83386-4-sean@poorly.run
> > 
> > Cc: Zain Wang <wzz@rock-chips.com>
> > Cc: Tomasz Figa <tfiga@chromium.org>
> > Signed-off-by: Sean Paul <seanpaul@chromium.org>
> > ---
> >  .../drm/bridge/analogix/analogix_dp_core.c    | 213 ++++++++-----
> >  .../drm/bridge/analogix/analogix_dp_core.h    |   2 +-
> >  drivers/gpu/drm/rockchip/Makefile             |   3 +-
> >  .../gpu/drm/rockchip/analogix_dp-rockchip.c   |  70 ++---
> >  drivers/gpu/drm/rockchip/rockchip_drm_fb.c    |  16 -
> >  drivers/gpu/drm/rockchip/rockchip_drm_psr.c   | 290 ------------------
> >  drivers/gpu/drm/rockchip/rockchip_drm_psr.h   |  30 --
> >  drivers/gpu/drm/rockchip/rockchip_drm_vop.c   |  33 +-
> >  include/drm/bridge/analogix_dp.h              |   4 +-
> >  9 files changed, 185 insertions(+), 476 deletions(-)
> >  delete mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_psr.c
> >  delete mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_psr.h
> > 
> > diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
> > index af34554a5a02..c503fe3bc3b3 100644
> > --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
> > +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
> > @@ -106,63 +106,13 @@ static int analogix_dp_detect_hpd(struct analogix_dp_device *dp)
> >  	return 0;
> >  }
> >  
> > -int analogix_dp_psr_enabled(struct analogix_dp_device *dp)
> > +bool analogix_dp_self_refresh_changed(struct analogix_dp_device *dp)
> >  {
> > -
> > -	return dp->psr_enable;
> > +	return dp->connector.state->self_refresh_changed;
> >  }
> > -EXPORT_SYMBOL_GPL(analogix_dp_psr_enabled);
> > +EXPORT_SYMBOL_GPL(analogix_dp_self_refresh_changed);
> >  
> > -int analogix_dp_enable_psr(struct analogix_dp_device *dp)
> > -{
> > -	struct edp_vsc_psr psr_vsc;
> > -
> > -	if (!dp->psr_enable)
> > -		return 0;
> > -
> > -	/* Prepare VSC packet as per EDP 1.4 spec, Table 6.9 */
> > -	memset(&psr_vsc, 0, sizeof(psr_vsc));
> > -	psr_vsc.sdp_header.HB0 = 0;
> > -	psr_vsc.sdp_header.HB1 = 0x7;
> > -	psr_vsc.sdp_header.HB2 = 0x2;
> > -	psr_vsc.sdp_header.HB3 = 0x8;
> > -
> > -	psr_vsc.DB0 = 0;
> > -	psr_vsc.DB1 = EDP_VSC_PSR_STATE_ACTIVE | EDP_VSC_PSR_CRC_VALUES_VALID;
> > -
> > -	return analogix_dp_send_psr_spd(dp, &psr_vsc, true);
> > -}
> > -EXPORT_SYMBOL_GPL(analogix_dp_enable_psr);
> > -
> > -int analogix_dp_disable_psr(struct analogix_dp_device *dp)
> > -{
> > -	struct edp_vsc_psr psr_vsc;
> > -	int ret;
> > -
> > -	if (!dp->psr_enable)
> > -		return 0;
> > -
> > -	/* Prepare VSC packet as per EDP 1.4 spec, Table 6.9 */
> > -	memset(&psr_vsc, 0, sizeof(psr_vsc));
> > -	psr_vsc.sdp_header.HB0 = 0;
> > -	psr_vsc.sdp_header.HB1 = 0x7;
> > -	psr_vsc.sdp_header.HB2 = 0x2;
> > -	psr_vsc.sdp_header.HB3 = 0x8;
> > -
> > -	psr_vsc.DB0 = 0;
> > -	psr_vsc.DB1 = 0;
> > -
> > -	ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, DP_SET_POWER_D0);
> > -	if (ret != 1) {
> > -		dev_err(dp->dev, "Failed to set DP Power0 %d\n", ret);
> > -		return ret;
> > -	}
> > -
> > -	return analogix_dp_send_psr_spd(dp, &psr_vsc, false);
> > -}
> > -EXPORT_SYMBOL_GPL(analogix_dp_disable_psr);
> > -
> > -static int analogix_dp_detect_sink_psr(struct analogix_dp_device *dp)
> > +static bool analogix_dp_detect_sink_psr(struct analogix_dp_device *dp)
> >  {
> >  	unsigned char psr_version;
> >  	int ret;
> > @@ -170,14 +120,11 @@ static int analogix_dp_detect_sink_psr(struct analogix_dp_device *dp)
> >  	ret = drm_dp_dpcd_readb(&dp->aux, DP_PSR_SUPPORT, &psr_version);
> >  	if (ret != 1) {
> >  		dev_err(dp->dev, "failed to get PSR version, disable it\n");
> > -		return ret;
> > +		return false;
> >  	}
> >  
> >  	dev_dbg(dp->dev, "Panel PSR version : %x\n", psr_version);
> > -
> > -	dp->psr_enable = (psr_version & DP_PSR_IS_SUPPORTED) ? true : false;
> > -
> > -	return 0;
> > +	return psr_version & DP_PSR_IS_SUPPORTED;
> >  }
> >  
> >  static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp)
> > @@ -200,7 +147,7 @@ static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp)
> >  	}
> >  
> >  	/* Main-Link transmitter remains active during PSR active states */
> > -	psr_en = DP_PSR_MAIN_LINK_ACTIVE | DP_PSR_CRC_VERIFICATION;
> > +	psr_en = DP_PSR_CRC_VERIFICATION;
> >  	ret = drm_dp_dpcd_writeb(&dp->aux, DP_PSR_EN_CFG, psr_en);
> >  	if (ret != 1) {
> >  		dev_err(dp->dev, "failed to set panel psr\n");
> > @@ -208,8 +155,7 @@ static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp)
> >  	}
> >  
> >  	/* Enable psr function */
> > -	psr_en = DP_PSR_ENABLE | DP_PSR_MAIN_LINK_ACTIVE |
> > -		 DP_PSR_CRC_VERIFICATION;
> > +	psr_en = DP_PSR_ENABLE | DP_PSR_CRC_VERIFICATION;
> >  	ret = drm_dp_dpcd_writeb(&dp->aux, DP_PSR_EN_CFG, psr_en);
> >  	if (ret != 1) {
> >  		dev_err(dp->dev, "failed to set panel psr\n");
> > @@ -218,10 +164,11 @@ static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp)
> >  
> >  	analogix_dp_enable_psr_crc(dp);
> >  
> > +	dp->psr_supported = true;
> > +
> >  	return 0;
> >  end:
> >  	dev_err(dp->dev, "enable psr fail, force to disable psr\n");
> > -	dp->psr_enable = false;
> >  
> >  	return ret;
> >  }
> > @@ -1036,25 +983,90 @@ static int analogix_dp_commit(struct analogix_dp_device *dp)
> >  		}
> >  	}
> >  
> > -	ret = analogix_dp_detect_sink_psr(dp);
> > -	if (ret)
> > -		return ret;
> > -
> >  	/* Check whether panel supports fast training */
> >  	ret = analogix_dp_fast_link_train_detection(dp);
> >  	if (ret)
> > -		dp->psr_enable = false;
> > +		return ret;
> >  
> > -	if (dp->psr_enable) {
> > +	if (analogix_dp_detect_sink_psr(dp)) {
> >  		ret = analogix_dp_enable_sink_psr(dp);
> >  		if (ret)
> >  			return ret;
> >  	}
> >  
> > +	return ret;
> > +}
> > +
> > +static int analogix_dp_enable_psr(struct analogix_dp_device *dp)
> > +{
> > +	struct edp_vsc_psr psr_vsc;
> > +	int ret;
> > +	u8 sink;
> > +
> > +	ret = drm_dp_dpcd_readb(&dp->aux, DP_PSR_STATUS, &sink);
> > +	if (ret != 1)
> > +		DRM_DEV_ERROR(dp->dev, "Failed to read psr status %d\n", ret);
> > +	else if (sink == DP_PSR_SINK_ACTIVE_RFB)
> > +		return 0;
> > +
> > +	/* Prepare VSC packet as per EDP 1.4 spec, Table 6.9 */
> > +	memset(&psr_vsc, 0, sizeof(psr_vsc));
> > +	psr_vsc.sdp_header.HB0 = 0;
> > +	psr_vsc.sdp_header.HB1 = 0x7;
> > +	psr_vsc.sdp_header.HB2 = 0x2;
> > +	psr_vsc.sdp_header.HB3 = 0x8;
> > +	psr_vsc.DB0 = 0;
> > +	psr_vsc.DB1 = EDP_VSC_PSR_STATE_ACTIVE | EDP_VSC_PSR_CRC_VALUES_VALID;
> > +
> > +	ret = analogix_dp_send_psr_spd(dp, &psr_vsc, true);
> > +	if (!ret)
> > +		analogix_dp_set_analog_power_down(dp, POWER_ALL, true);
> >  
> >  	return ret;
> >  }
> >  
> > +static int analogix_dp_disable_psr(struct analogix_dp_device *dp)
> > +{
> > +	struct edp_vsc_psr psr_vsc;
> > +	int ret;
> > +	u8 sink;
> > +
> > +	analogix_dp_set_analog_power_down(dp, POWER_ALL, false);
> > +
> > +	ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, DP_SET_POWER_D0);
> > +	if (ret != 1) {
> > +		DRM_DEV_ERROR(dp->dev, "Failed to set DP Power0 %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	ret = drm_dp_dpcd_readb(&dp->aux, DP_PSR_STATUS, &sink);
> > +	if (ret != 1) {
> > +		DRM_DEV_ERROR(dp->dev, "Failed to read psr status %d\n", ret);
> > +		return ret;
> > +	} else if (sink == DP_PSR_SINK_INACTIVE) {
> > +		DRM_DEV_ERROR(dp->dev, "sink inactive, skip disable psr");
> > +		return 0;
> > +	}
> > +
> > +	ret = analogix_dp_train_link(dp);
> > +	if (ret) {
> > +		DRM_DEV_ERROR(dp->dev, "Failed to train the link %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	/* Prepare VSC packet as per EDP 1.4 spec, Table 6.9 */
> > +	memset(&psr_vsc, 0, sizeof(psr_vsc));
> > +	psr_vsc.sdp_header.HB0 = 0;
> > +	psr_vsc.sdp_header.HB1 = 0x7;
> > +	psr_vsc.sdp_header.HB2 = 0x2;
> > +	psr_vsc.sdp_header.HB3 = 0x8;
> > +
> > +	psr_vsc.DB0 = 0;
> > +	psr_vsc.DB1 = 0;
> > +
> > +	return analogix_dp_send_psr_spd(dp, &psr_vsc, true);
> > +}
> > +
> >  /*
> >   * This function is a bit of a catch-all for panel preparation, hopefully
> >   * simplifying the logic of functions that need to prepare/unprepare the panel
> > @@ -1145,9 +1157,23 @@ analogix_dp_best_encoder(struct drm_connector *connector)
> >  	return dp->encoder;
> >  }
> >  
> > +
> > +int analogix_dp_atomic_check(struct drm_connector *connector,
> > +			     struct drm_connector_state *state)
> > +{
> > +	struct analogix_dp_device *dp = to_dp(connector);
> > +
> > +	state->self_refresh_aware = true;
> > +	if (state->self_refresh_active && !dp->psr_supported)
> > +		return -EINVAL;
> > +
> > +	return 0;
> > +}
> > +
> >  static const struct drm_connector_helper_funcs analogix_dp_connector_helper_funcs = {
> >  	.get_modes = analogix_dp_get_modes,
> >  	.best_encoder = analogix_dp_best_encoder,
> > +	.atomic_check = analogix_dp_atomic_check,
> >  };
> >  
> >  static enum drm_connector_status
> > @@ -1244,6 +1270,10 @@ static void analogix_dp_bridge_pre_enable(struct drm_bridge *bridge)
> >  	struct analogix_dp_device *dp = bridge->driver_private;
> >  	int ret;
> >  
> > +	/* Don't touch the panel if we're coming back from PSR */
> > +	if (dp->connector.state->self_refresh_changed)
> > +		return;
> > +
> >  	ret = analogix_dp_prepare_panel(dp, true, true);
> >  	if (ret)
> >  		DRM_ERROR("failed to setup the panel ret = %d\n", ret);
> > @@ -1309,6 +1339,14 @@ static void analogix_dp_bridge_enable(struct drm_bridge *bridge)
> >  	struct analogix_dp_device *dp = bridge->driver_private;
> >  	int timeout_loop = 0;
> >  
> > +	/* Not a full enable, just disable PSR and continue */
> > +	if (dp->connector.state->self_refresh_changed) {
> > +		int ret = analogix_dp_disable_psr(dp);
> > +		if (ret)
> > +			DRM_ERROR("Failed to disable psr %d\n", ret);
> > +		return;
> > +	}
> > +
> >  	if (dp->dpms_mode == DRM_MODE_DPMS_ON)
> >  		return;
> >  
> > @@ -1356,11 +1394,37 @@ static void analogix_dp_bridge_disable(struct drm_bridge *bridge)
> >  	if (ret)
> >  		DRM_ERROR("failed to setup the panel ret = %d\n", ret);
> >  
> > -	dp->psr_enable = false;
> >  	dp->fast_train_enable = false;
> > +	dp->psr_supported = false;
> >  	dp->dpms_mode = DRM_MODE_DPMS_OFF;
> >  }
> >  
> > +static void analogix_dp_bridge_atomic_disable(struct drm_bridge *bridge)
> > +{
> > +	struct analogix_dp_device *dp = bridge->driver_private;
> > +	struct drm_connector_state *conn_state = dp->connector.state;
> > +
> > +	/* Don't do a full disable on PSR transitions */
> > +	if (conn_state->crtc && conn_state->self_refresh_changed)
> > +		return;
> > +
> > +	analogix_dp_bridge_disable(bridge);
> > +}
> > +
> > +static void analogix_dp_bridge_post_disable(struct drm_bridge *bridge)
> > +{
> > +	struct analogix_dp_device *dp = bridge->driver_private;
> > +	struct drm_connector_state *conn_state = dp->connector.state;
> > +	int ret;
> > +
> > +	if (conn_state->self_refresh_active) {
> > +		ret = analogix_dp_enable_psr(dp);
> > +		if (ret)
> > +			DRM_ERROR("Failed to enable psr (%d)\n", ret);
> > +		return;
> > +	}
> > +}
> > +
> >  static void analogix_dp_bridge_mode_set(struct drm_bridge *bridge,
> >  				const struct drm_display_mode *orig_mode,
> >  				const struct drm_display_mode *mode)
> > @@ -1440,16 +1504,11 @@ static void analogix_dp_bridge_mode_set(struct drm_bridge *bridge,
> >  		video->interlaced = true;
> >  }
> >  
> > -static void analogix_dp_bridge_nop(struct drm_bridge *bridge)
> > -{
> > -	/* do nothing */
> > -}
> > -
> >  static const struct drm_bridge_funcs analogix_dp_bridge_funcs = {
> >  	.pre_enable = analogix_dp_bridge_pre_enable,
> >  	.enable = analogix_dp_bridge_enable,
> > -	.disable = analogix_dp_bridge_disable,
> > -	.post_disable = analogix_dp_bridge_nop,
> > +	.disable = analogix_dp_bridge_atomic_disable,
> > +	.post_disable = analogix_dp_bridge_post_disable,
> >  	.mode_set = analogix_dp_bridge_mode_set,
> >  	.attach = analogix_dp_bridge_attach,
> >  };
> > diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h
> > index 769255dc6e99..d937fa3d9ee8 100644
> > --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h
> > +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h
> > @@ -173,8 +173,8 @@ struct analogix_dp_device {
> >  	int			dpms_mode;
> >  	int			hpd_gpio;
> >  	bool                    force_hpd;
> > -	bool			psr_enable;
> >  	bool			fast_train_enable;
> > +	bool			psr_supported;
> >  
> >  	struct mutex		panel_lock;
> >  	bool			panel_is_modeset;
> > diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
> > index f6fc9d5dd0ad..a777726c23cb 100644
> > --- a/drivers/gpu/drm/rockchip/Makefile
> > +++ b/drivers/gpu/drm/rockchip/Makefile
> > @@ -4,8 +4,7 @@
> >  # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
> >  
> >  rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
> > -		rockchip_drm_gem.o rockchip_drm_psr.o \
> > -		rockchip_drm_vop.o rockchip_vop_reg.o
> > +		rockchip_drm_gem.o rockchip_drm_vop.o rockchip_vop_reg.o
> >  rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o
> >  
> >  rockchipdrm-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o
> > diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
> > index bc4423624209..8f42c38848db 100644
> > --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
> > +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
> > @@ -32,7 +32,6 @@
> >  #include <drm/bridge/analogix_dp.h>
> >  
> >  #include "rockchip_drm_drv.h"
> > -#include "rockchip_drm_psr.h"
> >  #include "rockchip_drm_vop.h"
> >  
> >  #define RK3288_GRF_SOC_CON6		0x25c
> > @@ -77,29 +76,6 @@ struct rockchip_dp_device {
> >  	struct analogix_dp_plat_data plat_data;
> >  };
> >  
> > -static int analogix_dp_psr_set(struct drm_encoder *encoder, bool enabled)
> > -{
> > -	struct rockchip_dp_device *dp = to_dp(encoder);
> > -	int ret;
> > -
> > -	if (!analogix_dp_psr_enabled(dp->adp))
> > -		return 0;
> > -
> > -	DRM_DEV_DEBUG(dp->dev, "%s PSR...\n", enabled ? "Entry" : "Exit");
> > -
> > -	ret = rockchip_drm_wait_vact_end(dp->encoder.crtc,
> > -					 PSR_WAIT_LINE_FLAG_TIMEOUT_MS);
> > -	if (ret) {
> > -		DRM_DEV_ERROR(dp->dev, "line flag interrupt did not arrive\n");
> > -		return -ETIMEDOUT;
> > -	}
> > -
> > -	if (enabled)
> > -		return analogix_dp_enable_psr(dp->adp);
> > -	else
> > -		return analogix_dp_disable_psr(dp->adp);
> > -}
> > -
> >  static int rockchip_dp_pre_init(struct rockchip_dp_device *dp)
> >  {
> >  	reset_control_assert(dp->rst);
> > @@ -130,21 +106,9 @@ static int rockchip_dp_poweron_start(struct analogix_dp_plat_data *plat_data)
> >  	return ret;
> >  }
> >  
> > -static int rockchip_dp_poweron_end(struct analogix_dp_plat_data *plat_data)
> > -{
> > -	struct rockchip_dp_device *dp = to_dp(plat_data);
> > -
> > -	return rockchip_drm_psr_inhibit_put(&dp->encoder);
> > -}
> > -
> >  static int rockchip_dp_powerdown(struct analogix_dp_plat_data *plat_data)
> >  {
> >  	struct rockchip_dp_device *dp = to_dp(plat_data);
> > -	int ret;
> > -
> > -	ret = rockchip_drm_psr_inhibit_get(&dp->encoder);
> > -	if (ret != 0)
> > -		return ret;
> >  
> >  	clk_disable_unprepare(dp->pclk);
> >  
> > @@ -190,6 +154,9 @@ static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder)
> >  	int ret;
> >  	u32 val;
> >  
> > +	if (analogix_dp_self_refresh_changed(dp->adp))
> > +		return;
> > +
> >  	ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, encoder);
> >  	if (ret < 0)
> >  		return;
> > @@ -214,9 +181,24 @@ static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder)
> >  	clk_disable_unprepare(dp->grfclk);
> >  }
> >  
> > -static void rockchip_dp_drm_encoder_nop(struct drm_encoder *encoder)
> > +static void rockchip_dp_drm_encoder_disable(struct drm_encoder *encoder)
> >  {
> > -	/* do nothing */
> > +	struct rockchip_dp_device *dp = to_dp(encoder);
> > +	struct drm_crtc *crtc;
> > +	int ret;
> > +
> > +	drm_for_each_crtc(crtc, dp->drm_dev) {
> > +		if (!(crtc->state->encoder_mask & drm_encoder_mask(encoder)))
> > +			continue;
> > +
> > +		if (!crtc->state->self_refresh_active)
> > +			continue;
> > +
> > +		ret = rockchip_drm_wait_vact_end(crtc,
> > +						 PSR_WAIT_LINE_FLAG_TIMEOUT_MS);
> > +		if (ret)
> > +			DRM_DEV_ERROR(dp->dev, "line flag irq timed out\n");
> > +	}
> >  }
> >  
> >  static int
> > @@ -246,7 +228,7 @@ static struct drm_encoder_helper_funcs rockchip_dp_encoder_helper_funcs = {
> >  	.mode_fixup = rockchip_dp_drm_encoder_mode_fixup,
> >  	.mode_set = rockchip_dp_drm_encoder_mode_set,
> >  	.enable = rockchip_dp_drm_encoder_enable,
> > -	.disable = rockchip_dp_drm_encoder_nop,
> > +	.disable = rockchip_dp_drm_encoder_disable,
> >  	.atomic_check = rockchip_dp_drm_encoder_atomic_check,
> >  };
> >  
> > @@ -338,23 +320,16 @@ static int rockchip_dp_bind(struct device *dev, struct device *master,
> >  
> >  	dp->plat_data.dev_type = dp->data->chip_type;
> >  	dp->plat_data.power_on_start = rockchip_dp_poweron_start;
> > -	dp->plat_data.power_on_end = rockchip_dp_poweron_end;
> >  	dp->plat_data.power_off = rockchip_dp_powerdown;
> >  	dp->plat_data.get_modes = rockchip_dp_get_modes;
> >  
> > -	ret = rockchip_drm_psr_register(&dp->encoder, analogix_dp_psr_set);
> > -	if (ret < 0)
> > -		goto err_cleanup_encoder;
> > -
> >  	dp->adp = analogix_dp_bind(dev, dp->drm_dev, &dp->plat_data);
> >  	if (IS_ERR(dp->adp)) {
> >  		ret = PTR_ERR(dp->adp);
> > -		goto err_unreg_psr;
> > +		goto err_cleanup_encoder;
> >  	}
> >  
> >  	return 0;
> > -err_unreg_psr:
> > -	rockchip_drm_psr_unregister(&dp->encoder);
> >  err_cleanup_encoder:
> >  	dp->encoder.funcs->destroy(&dp->encoder);
> >  	return ret;
> > @@ -366,7 +341,6 @@ static void rockchip_dp_unbind(struct device *dev, struct device *master,
> >  	struct rockchip_dp_device *dp = dev_get_drvdata(dev);
> >  
> >  	analogix_dp_unbind(dp->adp);
> > -	rockchip_drm_psr_unregister(&dp->encoder);
> >  	dp->encoder.funcs->destroy(&dp->encoder);
> >  
> >  	dp->adp = ERR_PTR(-ENODEV);
> > diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
> > index 97438bbbe389..7e121875e3c9 100644
> > --- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
> > +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
> > @@ -23,22 +23,10 @@
> >  #include "rockchip_drm_drv.h"
> >  #include "rockchip_drm_fb.h"
> >  #include "rockchip_drm_gem.h"
> > -#include "rockchip_drm_psr.h"
> > -
> > -static int rockchip_drm_fb_dirty(struct drm_framebuffer *fb,
> > -				 struct drm_file *file,
> > -				 unsigned int flags, unsigned int color,
> > -				 struct drm_clip_rect *clips,
> > -				 unsigned int num_clips)
> > -{
> > -	rockchip_drm_psr_flush_all(fb->dev);
> > -	return 0;
> > -}
> >  
> >  static const struct drm_framebuffer_funcs rockchip_drm_fb_funcs = {
> >  	.destroy       = drm_gem_fb_destroy,
> >  	.create_handle = drm_gem_fb_create_handle,
> > -	.dirty	       = rockchip_drm_fb_dirty,
> >  };
> >  
> >  static struct drm_framebuffer *
> > @@ -132,8 +120,6 @@ rockchip_atomic_helper_commit_tail_rpm(struct drm_atomic_state *old_state)
> >  {
> >  	struct drm_device *dev = old_state->dev;
> >  
> > -	rockchip_drm_psr_inhibit_get_state(old_state);
> > -
> >  	drm_atomic_helper_commit_modeset_disables(dev, old_state);
> >  
> >  	drm_atomic_helper_commit_modeset_enables(dev, old_state);
> > @@ -141,8 +127,6 @@ rockchip_atomic_helper_commit_tail_rpm(struct drm_atomic_state *old_state)
> >  	drm_atomic_helper_commit_planes(dev, old_state,
> >  					DRM_PLANE_COMMIT_ACTIVE_ONLY);
> >  
> > -	rockchip_drm_psr_inhibit_put_state(old_state);
> > -
> >  	drm_atomic_helper_commit_hw_done(old_state);
> >  
> >  	drm_atomic_helper_wait_for_vblanks(dev, old_state);
> > diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_psr.c b/drivers/gpu/drm/rockchip/rockchip_drm_psr.c
> > deleted file mode 100644
> > index a0c8bd235b67..000000000000
> > --- a/drivers/gpu/drm/rockchip/rockchip_drm_psr.c
> > +++ /dev/null
> > @@ -1,290 +0,0 @@
> > -/*
> > - * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> > - * Author: Yakir Yang <ykk@rock-chips.com>
> > - *
> > - * This software is licensed under the terms of the GNU General Public
> > - * License version 2, as published by the Free Software Foundation, and
> > - * may be copied, distributed, and modified under those terms.
> > - *
> > - * This program is distributed in the hope that it will be useful,
> > - * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > - * GNU General Public License for more details.
> > - */
> > -
> > -#include <drm/drmP.h>
> > -#include <drm/drm_atomic.h>
> > -#include <drm/drm_probe_helper.h>
> > -
> > -#include "rockchip_drm_drv.h"
> > -#include "rockchip_drm_psr.h"
> > -
> > -#define PSR_FLUSH_TIMEOUT_MS	100
> > -
> > -struct psr_drv {
> > -	struct list_head	list;
> > -	struct drm_encoder	*encoder;
> > -
> > -	struct mutex		lock;
> > -	int			inhibit_count;
> > -	bool			enabled;
> > -
> > -	struct delayed_work	flush_work;
> > -
> > -	int (*set)(struct drm_encoder *encoder, bool enable);
> > -};
> > -
> > -static struct psr_drv *find_psr_by_encoder(struct drm_encoder *encoder)
> > -{
> > -	struct rockchip_drm_private *drm_drv = encoder->dev->dev_private;
> > -	struct psr_drv *psr;
> > -
> > -	mutex_lock(&drm_drv->psr_list_lock);
> > -	list_for_each_entry(psr, &drm_drv->psr_list, list) {
> > -		if (psr->encoder == encoder)
> > -			goto out;
> > -	}
> > -	psr = ERR_PTR(-ENODEV);
> > -
> > -out:
> > -	mutex_unlock(&drm_drv->psr_list_lock);
> > -	return psr;
> > -}
> > -
> > -static int psr_set_state_locked(struct psr_drv *psr, bool enable)
> > -{
> > -	int ret;
> > -
> > -	if (psr->inhibit_count > 0)
> > -		return -EINVAL;
> > -
> > -	if (enable == psr->enabled)
> > -		return 0;
> > -
> > -	ret = psr->set(psr->encoder, enable);
> > -	if (ret)
> > -		return ret;
> > -
> > -	psr->enabled = enable;
> > -	return 0;
> > -}
> > -
> > -static void psr_flush_handler(struct work_struct *work)
> > -{
> > -	struct psr_drv *psr = container_of(to_delayed_work(work),
> > -					   struct psr_drv, flush_work);
> > -
> > -	mutex_lock(&psr->lock);
> > -	psr_set_state_locked(psr, true);
> > -	mutex_unlock(&psr->lock);
> > -}
> > -
> > -/**
> > - * rockchip_drm_psr_inhibit_put - release PSR inhibit on given encoder
> > - * @encoder: encoder to obtain the PSR encoder
> > - *
> > - * Decrements PSR inhibit count on given encoder. Should be called only
> > - * for a PSR inhibit count increment done before. If PSR inhibit counter
> > - * reaches zero, PSR flush work is scheduled to make the hardware enter
> > - * PSR mode in PSR_FLUSH_TIMEOUT_MS.
> > - *
> > - * Returns:
> > - * Zero on success, negative errno on failure.
> > - */
> > -int rockchip_drm_psr_inhibit_put(struct drm_encoder *encoder)
> > -{
> > -	struct psr_drv *psr = find_psr_by_encoder(encoder);
> > -
> > -	if (IS_ERR(psr))
> > -		return PTR_ERR(psr);
> > -
> > -	mutex_lock(&psr->lock);
> > -	--psr->inhibit_count;
> > -	WARN_ON(psr->inhibit_count < 0);
> > -	if (!psr->inhibit_count)
> > -		mod_delayed_work(system_wq, &psr->flush_work,
> > -				 PSR_FLUSH_TIMEOUT_MS);
> > -	mutex_unlock(&psr->lock);
> > -
> > -	return 0;
> > -}
> > -EXPORT_SYMBOL(rockchip_drm_psr_inhibit_put);
> > -
> > -void rockchip_drm_psr_inhibit_get_state(struct drm_atomic_state *state)
> > -{
> > -	struct drm_crtc *crtc;
> > -	struct drm_crtc_state *crtc_state;
> > -	struct drm_encoder *encoder;
> > -	u32 encoder_mask = 0;
> > -	int i;
> > -
> > -	for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> > -		encoder_mask |= crtc_state->encoder_mask;
> > -		encoder_mask |= crtc->state->encoder_mask;
> > -	}
> > -
> > -	drm_for_each_encoder_mask(encoder, state->dev, encoder_mask)
> > -		rockchip_drm_psr_inhibit_get(encoder);
> > -}
> > -EXPORT_SYMBOL(rockchip_drm_psr_inhibit_get_state);
> > -
> > -void rockchip_drm_psr_inhibit_put_state(struct drm_atomic_state *state)
> > -{
> > -	struct drm_crtc *crtc;
> > -	struct drm_crtc_state *crtc_state;
> > -	struct drm_encoder *encoder;
> > -	u32 encoder_mask = 0;
> > -	int i;
> > -
> > -	for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
> > -		encoder_mask |= crtc_state->encoder_mask;
> > -		encoder_mask |= crtc->state->encoder_mask;
> > -	}
> > -
> > -	drm_for_each_encoder_mask(encoder, state->dev, encoder_mask)
> > -		rockchip_drm_psr_inhibit_put(encoder);
> > -}
> > -EXPORT_SYMBOL(rockchip_drm_psr_inhibit_put_state);
> > -
> > -/**
> > - * rockchip_drm_psr_inhibit_get - acquire PSR inhibit on given encoder
> > - * @encoder: encoder to obtain the PSR encoder
> > - *
> > - * Increments PSR inhibit count on given encoder. This function guarantees
> > - * that after it returns PSR is turned off on given encoder and no PSR-related
> > - * hardware state change occurs at least until a matching call to
> > - * rockchip_drm_psr_inhibit_put() is done.
> > - *
> > - * Returns:
> > - * Zero on success, negative errno on failure.
> > - */
> > -int rockchip_drm_psr_inhibit_get(struct drm_encoder *encoder)
> > -{
> > -	struct psr_drv *psr = find_psr_by_encoder(encoder);
> > -
> > -	if (IS_ERR(psr))
> > -		return PTR_ERR(psr);
> > -
> > -	mutex_lock(&psr->lock);
> > -	psr_set_state_locked(psr, false);
> > -	++psr->inhibit_count;
> > -	mutex_unlock(&psr->lock);
> > -	cancel_delayed_work_sync(&psr->flush_work);
> > -
> > -	return 0;
> > -}
> > -EXPORT_SYMBOL(rockchip_drm_psr_inhibit_get);
> > -
> > -static void rockchip_drm_do_flush(struct psr_drv *psr)
> > -{
> > -	cancel_delayed_work_sync(&psr->flush_work);
> > -
> > -	mutex_lock(&psr->lock);
> > -	if (!psr_set_state_locked(psr, false))
> > -		mod_delayed_work(system_wq, &psr->flush_work,
> > -				 PSR_FLUSH_TIMEOUT_MS);
> > -	mutex_unlock(&psr->lock);
> > -}
> > -
> > -/**
> > - * rockchip_drm_psr_flush_all - force to flush all registered PSR encoders
> > - * @dev: drm device
> > - *
> > - * Disable the PSR function for all registered encoders, and then enable the
> > - * PSR function back after PSR_FLUSH_TIMEOUT. If encoder PSR state have been
> > - * changed during flush time, then keep the state no change after flush
> > - * timeout.
> > - *
> > - * Returns:
> > - * Zero on success, negative errno on failure.
> > - */
> > -void rockchip_drm_psr_flush_all(struct drm_device *dev)
> > -{
> > -	struct rockchip_drm_private *drm_drv = dev->dev_private;
> > -	struct psr_drv *psr;
> > -
> > -	mutex_lock(&drm_drv->psr_list_lock);
> > -	list_for_each_entry(psr, &drm_drv->psr_list, list)
> > -		rockchip_drm_do_flush(psr);
> > -	mutex_unlock(&drm_drv->psr_list_lock);
> > -}
> > -EXPORT_SYMBOL(rockchip_drm_psr_flush_all);
> > -
> > -/**
> > - * rockchip_drm_psr_register - register encoder to psr driver
> > - * @encoder: encoder that obtain the PSR function
> > - * @psr_set: call back to set PSR state
> > - *
> > - * The function returns with PSR inhibit counter initialized with one
> > - * and the caller (typically encoder driver) needs to call
> > - * rockchip_drm_psr_inhibit_put() when it becomes ready to accept PSR
> > - * enable request.
> > - *
> > - * Returns:
> > - * Zero on success, negative errno on failure.
> > - */
> > -int rockchip_drm_psr_register(struct drm_encoder *encoder,
> > -			int (*psr_set)(struct drm_encoder *, bool enable))
> > -{
> > -	struct rockchip_drm_private *drm_drv;
> > -	struct psr_drv *psr;
> > -
> > -	if (!encoder || !psr_set)
> > -		return -EINVAL;
> > -
> > -	drm_drv = encoder->dev->dev_private;
> > -
> > -	psr = kzalloc(sizeof(struct psr_drv), GFP_KERNEL);
> > -	if (!psr)
> > -		return -ENOMEM;
> > -
> > -	INIT_DELAYED_WORK(&psr->flush_work, psr_flush_handler);
> > -	mutex_init(&psr->lock);
> > -
> > -	psr->inhibit_count = 1;
> > -	psr->enabled = false;
> > -	psr->encoder = encoder;
> > -	psr->set = psr_set;
> > -
> > -	mutex_lock(&drm_drv->psr_list_lock);
> > -	list_add_tail(&psr->list, &drm_drv->psr_list);
> > -	mutex_unlock(&drm_drv->psr_list_lock);
> > -
> > -	return 0;
> > -}
> > -EXPORT_SYMBOL(rockchip_drm_psr_register);
> > -
> > -/**
> > - * rockchip_drm_psr_unregister - unregister encoder to psr driver
> > - * @encoder: encoder that obtain the PSR function
> > - * @psr_set: call back to set PSR state
> > - *
> > - * It is expected that the PSR inhibit counter is 1 when this function is
> > - * called, which corresponds to a state when related encoder has been
> > - * disconnected from any CRTCs and its driver called
> > - * rockchip_drm_psr_inhibit_get() to stop the PSR logic.
> > - *
> > - * Returns:
> > - * Zero on success, negative errno on failure.
> > - */
> > -void rockchip_drm_psr_unregister(struct drm_encoder *encoder)
> > -{
> > -	struct rockchip_drm_private *drm_drv = encoder->dev->dev_private;
> > -	struct psr_drv *psr, *n;
> > -
> > -	mutex_lock(&drm_drv->psr_list_lock);
> > -	list_for_each_entry_safe(psr, n, &drm_drv->psr_list, list) {
> > -		if (psr->encoder == encoder) {
> > -			/*
> > -			 * Any other value would mean that the encoder
> > -			 * is still in use.
> > -			 */
> > -			WARN_ON(psr->inhibit_count != 1);
> > -
> > -			list_del(&psr->list);
> > -			kfree(psr);
> > -		}
> > -	}
> > -	mutex_unlock(&drm_drv->psr_list_lock);
> > -}
> > -EXPORT_SYMBOL(rockchip_drm_psr_unregister);
> > diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_psr.h b/drivers/gpu/drm/rockchip/rockchip_drm_psr.h
> > deleted file mode 100644
> > index 25350ba3237b..000000000000
> > --- a/drivers/gpu/drm/rockchip/rockchip_drm_psr.h
> > +++ /dev/null
> > @@ -1,30 +0,0 @@
> > -/*
> > - * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
> > - * Author: Yakir Yang <ykk@rock-chips.com>
> > - *
> > - * This software is licensed under the terms of the GNU General Public
> > - * License version 2, as published by the Free Software Foundation, and
> > - * may be copied, distributed, and modified under those terms.
> > - *
> > - * This program is distributed in the hope that it will be useful,
> > - * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > - * GNU General Public License for more details.
> > - */
> > -
> > -#ifndef __ROCKCHIP_DRM_PSR___
> > -#define __ROCKCHIP_DRM_PSR___
> > -
> > -void rockchip_drm_psr_flush_all(struct drm_device *dev);
> > -
> > -int rockchip_drm_psr_inhibit_put(struct drm_encoder *encoder);
> > -int rockchip_drm_psr_inhibit_get(struct drm_encoder *encoder);
> > -
> > -void rockchip_drm_psr_inhibit_get_state(struct drm_atomic_state *state);
> > -void rockchip_drm_psr_inhibit_put_state(struct drm_atomic_state *state);
> > -
> > -int rockchip_drm_psr_register(struct drm_encoder *encoder,
> > -			int (*psr_set)(struct drm_encoder *, bool enable));
> > -void rockchip_drm_psr_unregister(struct drm_encoder *encoder);
> > -
> > -#endif /* __ROCKCHIP_DRM_PSR__ */
> > diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> > index c7d4c6073ea5..21fccda70356 100644
> > --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> > +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> > @@ -21,6 +21,7 @@
> >  #include <drm/drm_gem_framebuffer_helper.h>
> >  #include <drm/drm_plane_helper.h>
> >  #include <drm/drm_probe_helper.h>
> > +#include <drm/drm_self_refresh_helper.h>
> >  #ifdef CONFIG_DRM_ANALOGIX_DP
> >  #include <drm/bridge/analogix_dp.h>
> >  #endif
> > @@ -42,10 +43,11 @@
> >  #include "rockchip_drm_drv.h"
> >  #include "rockchip_drm_gem.h"
> >  #include "rockchip_drm_fb.h"
> > -#include "rockchip_drm_psr.h"
> >  #include "rockchip_drm_vop.h"
> >  #include "rockchip_rgb.h"
> >  
> > +#define VOP_SELF_REFRESH_ENTRY_DELAY_MS 100
> > +
> >  #define VOP_WIN_SET(vop, win, name, v) \
> >  		vop_reg_set(vop, &win->phy->name, win->base, ~0, v, #name)
> >  #define VOP_SCL_SET(vop, win, name, v) \
> > @@ -541,7 +543,7 @@ static void vop_core_clks_disable(struct vop *vop)
> >  	clk_disable(vop->hclk);
> >  }
> >  
> > -static int vop_enable(struct drm_crtc *crtc)
> > +static int vop_enable(struct drm_crtc *crtc, struct drm_crtc_state *old_state)
> >  {
> >  	struct vop *vop = to_vop(crtc);
> >  	int ret, i;
> > @@ -581,12 +583,18 @@ static int vop_enable(struct drm_crtc *crtc)
> >  	 * We need to make sure that all windows are disabled before we
> >  	 * enable the crtc. Otherwise we might try to scan from a destroyed
> >  	 * buffer later.
> > +	 *
> > +	 * In the case of enable-after-PSR, we don't need to worry about this
> > +	 * case since the buffer is guaranteed to be valid and disabling the
> > +	 * window will result in screen glitches on PSR exit.
> >  	 */
> > -	for (i = 0; i < vop->data->win_size; i++) {
> > -		struct vop_win *vop_win = &vop->win[i];
> > -		const struct vop_win_data *win = vop_win->data;
> > +	if (!old_state || !old_state->self_refresh_active) {
> > +		for (i = 0; i < vop->data->win_size; i++) {
> > +			struct vop_win *vop_win = &vop->win[i];
> > +			const struct vop_win_data *win = vop_win->data;
> >  
> > -		VOP_WIN_SET(vop, win, enable, 0);
> > +			VOP_WIN_SET(vop, win, enable, 0);
> > +		}
> >  	}
> >  	spin_unlock(&vop->reg_lock);
> >  
> > @@ -937,12 +945,10 @@ static void vop_plane_atomic_async_update(struct drm_plane *plane,
> >  	}
> >  
> >  	if (vop->is_enabled) {
> > -		rockchip_drm_psr_inhibit_get_state(new_state->state);
> >  		vop_plane_atomic_update(plane, plane->state);
> >  		spin_lock(&vop->reg_lock);
> >  		vop_cfg_done(vop);
> >  		spin_unlock(&vop->reg_lock);
> > -		rockchip_drm_psr_inhibit_put_state(new_state->state);
> >  	}
> >  
> >  	plane->funcs->atomic_destroy_state(plane, plane_state);
> > @@ -1035,7 +1041,7 @@ static void vop_crtc_atomic_enable(struct drm_crtc *crtc,
> >  
> >  	WARN_ON(vop->event);
> >  
> > -	ret = vop_enable(crtc);
> > +	ret = vop_enable(crtc, old_state);
> >  	if (ret) {
> >  		mutex_unlock(&vop->vop_lock);
> >  		DRM_DEV_ERROR(vop->dev, "Failed to enable vop (%d)\n", ret);
> > @@ -1509,6 +1515,13 @@ static int vop_create_crtc(struct vop *vop)
> >  	init_completion(&vop->line_flag_completion);
> >  	crtc->port = port;
> >  
> > +	ret = drm_self_refresh_helper_register(crtc,
> > +					       VOP_SELF_REFRESH_ENTRY_DELAY_MS);
> > +	if (ret)
> > +		DRM_DEV_DEBUG_KMS(vop->dev,
> > +			"Failed to register %s with SR helpers %d, ignoring\n",
> > +			crtc->name, ret);
> > +
> >  	return 0;
> >  
> >  err_cleanup_crtc:
> > @@ -1526,6 +1539,8 @@ static void vop_destroy_crtc(struct vop *vop)
> >  	struct drm_device *drm_dev = vop->drm_dev;
> >  	struct drm_plane *plane, *tmp;
> >  
> > +	drm_self_refresh_helper_unregister(crtc);
> > +
> >  	of_node_put(crtc->port);
> >  
> >  	/*
> > diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h
> > index 475b706b49de..b6cc654143d7 100644
> > --- a/include/drm/bridge/analogix_dp.h
> > +++ b/include/drm/bridge/analogix_dp.h
> > @@ -42,9 +42,7 @@ struct analogix_dp_plat_data {
> >  			 struct drm_connector *);
> >  };
> >  
> > -int analogix_dp_psr_enabled(struct analogix_dp_device *dp);
> > -int analogix_dp_enable_psr(struct analogix_dp_device *dp);
> > -int analogix_dp_disable_psr(struct analogix_dp_device *dp);
> > +bool analogix_dp_self_refresh_changed(struct analogix_dp_device *dp);
> >  
> >  int analogix_dp_resume(struct analogix_dp_device *dp);
> >  int analogix_dp_suspend(struct analogix_dp_device *dp);
> > 
> 
> 
> 
> 

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 3/5] drm/rockchip: Use the helpers for PSR
  2019-03-29 19:00     ` Sean Paul
@ 2019-03-29 19:02       ` Heiko Stübner
  2019-03-29 19:12         ` Sean Paul
  0 siblings, 1 reply; 30+ messages in thread
From: Heiko Stübner @ 2019-03-29 19:02 UTC (permalink / raw)
  To: Sean Paul
  Cc: Zain Wang, David Airlie, dri-devel, Tomasz Figa, Sean Paul,
	Laurent Pinchart

Am Freitag, 29. März 2019, 20:00:10 CET schrieb Sean Paul:
> On Fri, Mar 29, 2019 at 07:51:51PM +0100, Heiko Stübner wrote:
> > Hi,
> > 
> > Am Dienstag, 26. März 2019, 21:44:56 CET schrieb Sean Paul:
> > > From: Sean Paul <seanpaul@chromium.org>
> > > 
> > > Instead of rolling our own implementation for tracking when PSR should
> > > be [in]active, use the new self refresh helpers to do the heavy lifting.
> > 
> > I only got patches 3-5 and had to pull 1+2 from patchwork, the following
> > applies to the whole series though.
> > 
> > While my Kevin display does still generally work with these 5 patches
> > applied functionality has regressed somehow.
> > 
> > Environment is a standard Debian with framebuffer console and sddm
> > login manager.
> 
> Ahh, this is probably b/c fb_dirty is not triggering PSR exit. I had it
> working in v1, but removed it in v2 since Daniel suggested we require the
> fb_dirty helpers. I neglected to add support for the fb_dirty helpers, so fbcon
> updates won't cause the screen to refresh. I'll add the helpers in v3 and make
> sure I test it out with fbcon.
> 
> Thanks for reporting this!

The cursor-not-blinking case above was X11+modesetting+sddm though, so
not directly fbcon-related probably ... 2nd issue?

Heiko


_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 3/5] drm/rockchip: Use the helpers for PSR
  2019-03-29 19:02       ` Heiko Stübner
@ 2019-03-29 19:12         ` Sean Paul
  2019-03-29 19:24           ` Daniel Vetter
  0 siblings, 1 reply; 30+ messages in thread
From: Sean Paul @ 2019-03-29 19:12 UTC (permalink / raw)
  To: Heiko Stübner
  Cc: Zain Wang, David Airlie, dri-devel, Tomasz Figa, Sean Paul,
	Laurent Pinchart, Sean Paul

On Fri, Mar 29, 2019 at 08:02:17PM +0100, Heiko Stübner wrote:
> Am Freitag, 29. März 2019, 20:00:10 CET schrieb Sean Paul:
> > On Fri, Mar 29, 2019 at 07:51:51PM +0100, Heiko Stübner wrote:
> > > Hi,
> > > 
> > > Am Dienstag, 26. März 2019, 21:44:56 CET schrieb Sean Paul:
> > > > From: Sean Paul <seanpaul@chromium.org>
> > > > 
> > > > Instead of rolling our own implementation for tracking when PSR should
> > > > be [in]active, use the new self refresh helpers to do the heavy lifting.
> > > 
> > > I only got patches 3-5 and had to pull 1+2 from patchwork, the following
> > > applies to the whole series though.
> > > 
> > > While my Kevin display does still generally work with these 5 patches
> > > applied functionality has regressed somehow.
> > > 
> > > Environment is a standard Debian with framebuffer console and sddm
> > > login manager.
> > 
> > Ahh, this is probably b/c fb_dirty is not triggering PSR exit. I had it
> > working in v1, but removed it in v2 since Daniel suggested we require the
> > fb_dirty helpers. I neglected to add support for the fb_dirty helpers, so fbcon
> > updates won't cause the screen to refresh. I'll add the helpers in v3 and make
> > sure I test it out with fbcon.
> > 
> > Thanks for reporting this!
> 
> The cursor-not-blinking case above was X11+modesetting+sddm though, so
> not directly fbcon-related probably ... 2nd issue?

Yep, definitely could be. If you have the chance, could you collect some
DRM_DEBUG_KMS logs for the second issue to see what's going on? My guess is
there's a legacy path (update_plane?) that's somehow not triggering psr exit.

Sean

> 
> Heiko
> 
> 

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-03-29 18:10           ` Sean Paul
@ 2019-03-29 19:21             ` Daniel Vetter
  2019-04-01 13:49               ` Sean Paul
  0 siblings, 1 reply; 30+ messages in thread
From: Daniel Vetter @ 2019-03-29 19:21 UTC (permalink / raw)
  To: Sean Paul
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul, dri-devel

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



-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 3/5] drm/rockchip: Use the helpers for PSR
  2019-03-29 19:12         ` Sean Paul
@ 2019-03-29 19:24           ` Daniel Vetter
  0 siblings, 0 replies; 30+ messages in thread
From: Daniel Vetter @ 2019-03-29 19:24 UTC (permalink / raw)
  To: Sean Paul
  Cc: Zain Wang, David Airlie, dri-devel, Tomasz Figa, Sean Paul,
	Laurent Pinchart

On Fri, Mar 29, 2019 at 8:12 PM Sean Paul <sean@poorly.run> wrote:
>
> On Fri, Mar 29, 2019 at 08:02:17PM +0100, Heiko Stübner wrote:
> > Am Freitag, 29. März 2019, 20:00:10 CET schrieb Sean Paul:
> > > On Fri, Mar 29, 2019 at 07:51:51PM +0100, Heiko Stübner wrote:
> > > > Hi,
> > > >
> > > > Am Dienstag, 26. März 2019, 21:44:56 CET schrieb Sean Paul:
> > > > > From: Sean Paul <seanpaul@chromium.org>
> > > > >
> > > > > Instead of rolling our own implementation for tracking when PSR should
> > > > > be [in]active, use the new self refresh helpers to do the heavy lifting.
> > > >
> > > > I only got patches 3-5 and had to pull 1+2 from patchwork, the following
> > > > applies to the whole series though.
> > > >
> > > > While my Kevin display does still generally work with these 5 patches
> > > > applied functionality has regressed somehow.
> > > >
> > > > Environment is a standard Debian with framebuffer console and sddm
> > > > login manager.
> > >
> > > Ahh, this is probably b/c fb_dirty is not triggering PSR exit. I had it
> > > working in v1, but removed it in v2 since Daniel suggested we require the
> > > fb_dirty helpers. I neglected to add support for the fb_dirty helpers, so fbcon
> > > updates won't cause the screen to refresh. I'll add the helpers in v3 and make
> > > sure I test it out with fbcon.
> > >
> > > Thanks for reporting this!
> >
> > The cursor-not-blinking case above was X11+modesetting+sddm though, so
> > not directly fbcon-related probably ... 2nd issue?
>
> Yep, definitely could be. If you have the chance, could you collect some
> DRM_DEBUG_KMS logs for the second issue to see what's going on? My guess is
> there's a legacy path (update_plane?) that's somehow not triggering psr exit.

fb_dirty helper is also for userspace kms. It's just that you get a
nice combo with the helpers that also takes care of fbcon (which is a
separate path if you don't bother with the dirty helpers but roll your
own for everything). And yeah the v2 design is a package, if you
handroll your own fb_dirty the magic won't work :-)
-Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-03-29 19:21             ` Daniel Vetter
@ 2019-04-01 13:49               ` Sean Paul
  2019-04-02  7:49                 ` Daniel Vetter
  0 siblings, 1 reply; 30+ messages in thread
From: Sean Paul @ 2019-04-01 13:49 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul, dri-devel, Sean Paul

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

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-04-01 13:49               ` Sean Paul
@ 2019-04-02  7:49                 ` Daniel Vetter
  2019-04-02 13:24                   ` Sean Paul
  2019-04-02 14:16                   ` Ville Syrjälä
  0 siblings, 2 replies; 30+ messages in thread
From: Daniel Vetter @ 2019-04-02  7:49 UTC (permalink / raw)
  To: Sean Paul
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul, dri-devel

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

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-03-26 20:44 [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers Sean Paul
                   ` (5 preceding siblings ...)
  2019-03-28 14:42 ` Dan Carpenter
@ 2019-04-02  8:55 ` Neil Armstrong
  2019-04-02  9:08   ` Daniel Vetter
  2019-04-02  9:53 ` Daniel Vetter
  7 siblings, 1 reply; 30+ messages in thread
From: Neil Armstrong @ 2019-04-02  8:55 UTC (permalink / raw)
  To: Sean Paul, dri-devel
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul

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
> 

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-04-02  8:55 ` Neil Armstrong
@ 2019-04-02  9:08   ` Daniel Vetter
  2019-04-02  9:45     ` Neil Armstrong
  0 siblings, 1 reply; 30+ messages in thread
From: Daniel Vetter @ 2019-04-02  9:08 UTC (permalink / raw)
  To: Neil Armstrong
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul, dri-devel, Sean Paul

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

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-04-02  9:08   ` Daniel Vetter
@ 2019-04-02  9:45     ` Neil Armstrong
  2019-04-02  9:50       ` Daniel Vetter
  0 siblings, 1 reply; 30+ messages in thread
From: Neil Armstrong @ 2019-04-02  9:45 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul, dri-devel, Sean Paul

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
> 

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-04-02  9:45     ` Neil Armstrong
@ 2019-04-02  9:50       ` Daniel Vetter
  0 siblings, 0 replies; 30+ messages in thread
From: Daniel Vetter @ 2019-04-02  9:50 UTC (permalink / raw)
  To: Neil Armstrong
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul, dri-devel, Sean Paul

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
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-03-26 20:44 [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers Sean Paul
                   ` (6 preceding siblings ...)
  2019-04-02  8:55 ` Neil Armstrong
@ 2019-04-02  9:53 ` Daniel Vetter
  7 siblings, 0 replies; 30+ messages in thread
From: Daniel Vetter @ 2019-04-02  9:53 UTC (permalink / raw)
  To: Sean Paul
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul, dri-devel

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
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-04-02  7:49                 ` Daniel Vetter
@ 2019-04-02 13:24                   ` Sean Paul
  2019-04-02 16:05                     ` Daniel Vetter
  2019-04-02 14:16                   ` Ville Syrjälä
  1 sibling, 1 reply; 30+ messages in thread
From: Sean Paul @ 2019-04-02 13:24 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul, dri-devel, Sean Paul

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

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-04-02  7:49                 ` Daniel Vetter
  2019-04-02 13:24                   ` Sean Paul
@ 2019-04-02 14:16                   ` Ville Syrjälä
  1 sibling, 0 replies; 30+ messages in thread
From: Ville Syrjälä @ 2019-04-02 14:16 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul, dri-devel, Sean Paul

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.

-- 
Ville Syrjälä
Intel
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-04-02 13:24                   ` Sean Paul
@ 2019-04-02 16:05                     ` Daniel Vetter
  2019-04-02 16:47                       ` Sean Paul
  0 siblings, 1 reply; 30+ messages in thread
From: Daniel Vetter @ 2019-04-02 16:05 UTC (permalink / raw)
  To: Sean Paul
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul, dri-devel

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



-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-04-02 16:05                     ` Daniel Vetter
@ 2019-04-02 16:47                       ` Sean Paul
  2019-04-03  6:52                         ` Daniel Vetter
  0 siblings, 1 reply; 30+ messages in thread
From: Sean Paul @ 2019-04-02 16:47 UTC (permalink / raw)
  To: Daniel Vetter
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul, dri-devel, Sean Paul

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

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers
  2019-04-02 16:47                       ` Sean Paul
@ 2019-04-03  6:52                         ` Daniel Vetter
  0 siblings, 0 replies; 30+ messages in thread
From: Daniel Vetter @ 2019-04-03  6:52 UTC (permalink / raw)
  To: Sean Paul
  Cc: Zain Wang, David Airlie, Jose Souza, Tomasz Figa, Maxime Ripard,
	Sean Paul, dri-devel

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
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 30+ messages in thread

end of thread, other threads:[~2019-04-03  6:52 UTC | newest]

Thread overview: 30+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-03-26 20:44 [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers Sean Paul
2019-03-26 20:44 ` [PATCH v2 2/5] drm/rockchip: Check for fast link training before enabling psr Sean Paul
2019-03-26 20:44 ` [PATCH v2 3/5] drm/rockchip: Use the helpers for PSR Sean Paul
2019-03-29 18:51   ` Heiko Stübner
2019-03-29 19:00     ` Sean Paul
2019-03-29 19:02       ` Heiko Stübner
2019-03-29 19:12         ` Sean Paul
2019-03-29 19:24           ` Daniel Vetter
2019-03-26 20:44 ` [PATCH v2 4/5] drm/rockchip: Don't fully disable vop on self refresh Sean Paul
2019-03-26 20:44 ` [PATCH v2 5/5] drm/rockchip: Use drm_atomic_helper_commit_tail_rpm Sean Paul
2019-03-27 18:15 ` [PATCH v2 1/5] drm: Add helpers to kick off self refresh mode in drivers Daniel Vetter
2019-03-28 21:03   ` Sean Paul
2019-03-29  8:21     ` Daniel Vetter
2019-03-29 13:16       ` Sean Paul
2019-03-29 15:36         ` Daniel Vetter
2019-03-29 18:10           ` Sean Paul
2019-03-29 19:21             ` Daniel Vetter
2019-04-01 13:49               ` Sean Paul
2019-04-02  7:49                 ` Daniel Vetter
2019-04-02 13:24                   ` Sean Paul
2019-04-02 16:05                     ` Daniel Vetter
2019-04-02 16:47                       ` Sean Paul
2019-04-03  6:52                         ` Daniel Vetter
2019-04-02 14:16                   ` Ville Syrjälä
2019-03-28 14:42 ` Dan Carpenter
2019-04-02  8:55 ` Neil Armstrong
2019-04-02  9:08   ` Daniel Vetter
2019-04-02  9:45     ` Neil Armstrong
2019-04-02  9:50       ` Daniel Vetter
2019-04-02  9:53 ` Daniel Vetter

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).