diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
index 17ca7f8bf3d3..d218a113bd52 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_psr_helper.c
+   :doc: overview
+
+.. kernel-doc:: drivers/gpu/drm/drm_psr_helper.c
+   :export:
+
 Framebuffer CMA Helper Functions Reference
 ==========================================
 
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 1ac55c65eac0..bff80fb946c7 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -19,7 +19,7 @@ drm-y       :=	drm_auth.o drm_bufs.o drm_cache.o \
 		drm_plane.o drm_color_mgmt.o drm_print.o \
 		drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
 		drm_syncobj.o drm_lease.o drm_writeback.o drm_client.o \
-		drm_atomic_uapi.o
+		drm_atomic_uapi.o drm_psr_helper.o
 
 drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
 drm-$(CONFIG_DRM_VM) += drm_vm.o
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index c53ecbd9abdd..f5284d55f170 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_psr_helper.h>
 #include <drm/drm_writeback.h>
 #include <drm/drm_damage_helper.h>
 #include <linux/dma-fence.h>
@@ -2746,6 +2747,10 @@ int drm_atomic_helper_update_plane(struct drm_plane *plane,
 	struct drm_plane_state *plane_state;
 	int ret = 0;
 
+	ret = drm_psr_helper_flush(plane->dev, ctx);
+	if (ret)
+		return ret;
+
 	state = drm_atomic_state_alloc(plane->dev);
 	if (!state)
 		return -ENOMEM;
@@ -2797,6 +2802,10 @@ int drm_atomic_helper_disable_plane(struct drm_plane *plane,
 	struct drm_plane_state *plane_state;
 	int ret = 0;
 
+	ret = drm_psr_helper_flush(plane->dev, ctx);
+	if (ret)
+		return ret;
+
 	state = drm_atomic_state_alloc(plane->dev);
 	if (!state)
 		return -ENOMEM;
@@ -2934,11 +2943,16 @@ int drm_atomic_helper_set_config(struct drm_mode_set *set,
 	struct drm_crtc *crtc = set->crtc;
 	int ret = 0;
 
+	ret = drm_psr_helper_flush(crtc->dev, ctx);
+	if (ret)
+		return ret;
+
 	state = drm_atomic_state_alloc(crtc->dev);
 	if (!state)
 		return -ENOMEM;
 
 	state->acquire_ctx = ctx;
+
 	ret = __drm_atomic_helper_set_config(set, state);
 	if (ret != 0)
 		goto fail;
@@ -3139,6 +3153,10 @@ void drm_atomic_helper_shutdown(struct drm_device *dev)
 
 	DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, 0, ret);
 
+	ret = drm_psr_helper_flush(dev, &ctx);
+	if (ret)
+		DRM_ERROR("PSR flush during shutdown failed with %i\n", ret);
+
 	ret = __drm_atomic_helper_disable_all(dev, &ctx, true);
 	if (ret)
 		DRM_ERROR("Disabling all crtc's during unload failed with %i\n", ret);
@@ -3271,6 +3289,10 @@ struct drm_atomic_state *drm_atomic_helper_suspend(struct drm_device *dev)
 
 	DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, 0, err);
 
+	err = drm_psr_helper_flush(dev, &ctx);
+	if (err)
+		goto unlock;
+
 	state = drm_atomic_helper_duplicate_state(dev, &ctx);
 	if (IS_ERR(state))
 		goto unlock;
@@ -3436,6 +3458,10 @@ int drm_atomic_helper_page_flip(struct drm_crtc *crtc,
 	struct drm_atomic_state *state;
 	int ret = 0;
 
+	ret = drm_psr_helper_flush(crtc->dev, ctx);
+	if (ret)
+		return ret;
+
 	state = drm_atomic_state_alloc(plane->dev);
 	if (!state)
 		return -ENOMEM;
@@ -3481,6 +3507,10 @@ int drm_atomic_helper_page_flip_target(struct drm_crtc *crtc,
 	struct drm_crtc_state *crtc_state;
 	int ret = 0;
 
+	ret = drm_psr_helper_flush(plane->dev, ctx);
+	if (ret)
+		return ret;
+
 	state = drm_atomic_state_alloc(plane->dev);
 	if (!state)
 		return -ENOMEM;
@@ -3532,6 +3562,10 @@ int drm_atomic_helper_legacy_gamma_set(struct drm_crtc *crtc,
 	int i, ret = 0;
 	bool replaced;
 
+	ret = drm_psr_helper_flush(dev, ctx);
+	if (ret)
+		return ret;
+
 	state = drm_atomic_state_alloc(crtc->dev);
 	if (!state)
 		return -ENOMEM;
diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
index 4eb81f10bc54..21d7771d6ec7 100644
--- a/drivers/gpu/drm/drm_atomic_uapi.c
+++ b/drivers/gpu/drm/drm_atomic_uapi.c
@@ -29,6 +29,7 @@
 #include <drm/drm_atomic_uapi.h>
 #include <drm/drm_atomic.h>
 #include <drm/drm_print.h>
+#include <drm/drm_psr_helper.h>
 #include <drm/drm_drv.h>
 #include <drm/drm_writeback.h>
 #include <drm/drm_vblank.h>
@@ -1314,6 +1315,10 @@ int drm_mode_atomic_ioctl(struct drm_device *dev,
 	fence_state = NULL;
 	num_fences = 0;
 
+	ret = drm_psr_helper_flush(dev, state->acquire_ctx);
+	if (ret)
+		goto out;
+
 	for (i = 0; i < arg->count_objs; i++) {
 		uint32_t obj_id, count_props;
 		struct drm_mode_object *obj;
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
index 04d23cb430bf..a56ae2acda90 100644
--- a/drivers/gpu/drm/drm_fb_helper.c
+++ b/drivers/gpu/drm/drm_fb_helper.c
@@ -41,6 +41,7 @@
 #include <drm/drm_crtc_helper.h>
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_psr_helper.h>
 
 #include "drm_crtc_internal.h"
 #include "drm_crtc_helper_internal.h"
@@ -406,6 +407,10 @@ static int restore_fbdev_mode_atomic(struct drm_fb_helper *fb_helper, bool activ
 
 	state->acquire_ctx = &ctx;
 retry:
+	ret = drm_psr_helper_flush(dev, state->acquire_ctx);
+	if (ret)
+		goto out_state;
+
 	drm_for_each_plane(plane, dev) {
 		plane_state = drm_atomic_get_plane_state(state, plane);
 		if (IS_ERR(plane_state)) {
@@ -1442,6 +1447,10 @@ static int setcmap_atomic(struct fb_cmap *cmap, struct fb_info *info)
 
 	state->acquire_ctx = &ctx;
 retry:
+	ret = drm_psr_helper_flush(dev, &ctx);
+	if (ret)
+		goto out_state;
+
 	for (i = 0; i < fb_helper->crtc_count; i++) {
 		crtc = fb_helper->crtc_info[i].mode_set.crtc;
 
diff --git a/drivers/gpu/drm/drm_framebuffer.c b/drivers/gpu/drm/drm_framebuffer.c
index d8d75e25f6fb..43b029a88578 100644
--- a/drivers/gpu/drm/drm_framebuffer.c
+++ b/drivers/gpu/drm/drm_framebuffer.c
@@ -27,6 +27,7 @@
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_uapi.h>
 #include <drm/drm_print.h>
+#include <drm/drm_psr_helper.h>
 #include <drm/drm_util.h>
 
 #include "drm_internal.h"
@@ -572,6 +573,7 @@ int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
 	struct drm_clip_rect __user *clips_ptr;
 	struct drm_clip_rect *clips = NULL;
 	struct drm_mode_fb_dirty_cmd *r = data;
+	struct drm_modeset_acquire_ctx ctx;
 	struct drm_framebuffer *fb;
 	unsigned flags;
 	int num_clips;
@@ -580,10 +582,24 @@ int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
 	if (!drm_core_check_feature(dev, DRIVER_MODESET))
 		return -EOPNOTSUPP;
 
+	drm_modeset_acquire_init(&ctx, 0);
+
 	fb = drm_framebuffer_lookup(dev, file_priv, r->fb_id);
 	if (!fb)
 		return -ENOENT;
 
+retry:
+	ret = drm_psr_helper_flush(fb->dev, &ctx);
+	if (ret) {
+		if (ret == -EDEADLK) {
+			ret = drm_modeset_backoff(&ctx);
+			if (!ret)
+				goto retry;
+		} else {
+			goto out_err1;
+		}
+	}
+
 	num_clips = r->num_clips;
 	clips_ptr = (struct drm_clip_rect __user *)(unsigned long)r->clips_ptr;
 
@@ -630,6 +646,8 @@ int drm_mode_dirtyfb_ioctl(struct drm_device *dev,
 	kfree(clips);
 out_err1:
 	drm_framebuffer_put(fb);
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
 
 	return ret;
 }
diff --git a/drivers/gpu/drm/drm_psr_helper.c b/drivers/gpu/drm/drm_psr_helper.c
new file mode 100644
index 000000000000..0b57a2a53075
--- /dev/null
+++ b/drivers/gpu/drm/drm_psr_helper.c
@@ -0,0 +1,343 @@
+/* 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_device.h>
+#include <drm/drm_mode_config.h>
+#include <drm/drm_modeset_lock.h>
+#include <drm/drm_print.h>
+#include <drm/drm_psr_helper.h>
+#include <linux/bitops.h>
+#include <linux/mutex.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 (PSR) support. Drivers are
+ * responsible for intializing and cleaning up the PSR helpers on load/unload.
+ * When drivers identify a display that supports self refreshing (eDP or MIPI
+ * command mode), it should register the affected connector with the PSR
+ * helpers.
+ *
+ * Once a connector is registered, the PSR helpers will monitor activity and
+ * call back into the driver to enable/disable PSR 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 PSR on the panel instead of power-
+ * cycling it.
+ *
+ * Drivers may choose to fully disable their crtc/encoder/bridge hardware, or
+ * they can use the "psr_transition" flag in crtc and connector state if they
+ * want to enter low power mode without full disable (in case full
+ * disable/enable is too slow).
+ *
+ * PSR will be deactivated if there are any atomic updates, even updates that do
+ * not affect the connectors which are self refreshing. Supporting this is
+ * possible but non-trivial due to sharing of hardware resources. Similarly, if
+ * a crtc is driving multiple connectors, PSR will not be initiated on any of
+ * those connectors.
+ */
+
+struct drm_psr_state {
+	struct drm_device *dev;
+	struct drm_modeset_lock mutex;
+	struct delayed_work entry_work;
+	struct drm_atomic_state *save_state;
+	unsigned int entry_delay_ms;
+};
+
+static void drm_psr_helper_entry_work(struct work_struct *work)
+{
+	struct drm_psr_state *psr_state = container_of(to_delayed_work(work),
+						       struct drm_psr_state,
+						       entry_work);
+	struct drm_atomic_state *save_state;
+	struct drm_device *dev = psr_state->dev;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_atomic_state *state;
+	struct drm_connector *conn;
+	struct drm_connector_list_iter conn_iter;
+	struct drm_connector_state *conn_state;
+	struct drm_crtc *crtc;
+	struct drm_crtc_state *crtc_state;
+	bool commit = false;
+	int ret, i;
+
+	DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, 0, ret);
+
+	ret = drm_modeset_lock(&psr_state->mutex, &ctx);
+	if (ret)
+		goto out;
+
+	/*
+	 * The only way this can happen is if we schedule the worker while it's
+	 * already running and that timeout subsequently elapses. Since we hold
+	 * the psr_state->mutex when scheduling, we also know where the worker
+	 * is sitting in its execution (hint: look up). In this case, it's
+	 * possible for the entry worker to run twice for the same commit. Since
+	 * the hardware hasn't changed since the last save state, just kick out.
+	 */
+	if (psr_state->save_state)
+		goto out;
+
+	state = drm_atomic_state_alloc(dev);
+	if (!state) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	save_state = drm_atomic_helper_duplicate_state(dev, &ctx);
+
+	/*
+	 * Now that we have the current the HW state saved, we have to flip the
+	 * psr_transition bit so we know what type of enable we're dealing with
+	 * when coming back on.
+	 *
+	 * NOTE: We don't check conn->capable here since that could change out
+	 * from under us. We'll trust the atomic core to only call enable if
+	 * necessary (ie: only for those connectors/crtcs that currently have
+	 * psr enabled).
+	 */
+	if (IS_ERR(save_state)) {
+		ret = PTR_ERR(save_state);
+		goto out;
+	}
+	for_each_new_connector_in_state(save_state, conn, conn_state, i) {
+		if (!conn_state->crtc)
+			continue;
+		conn_state->psr_transition = true;
+	}
+	for_each_new_crtc_in_state(save_state, crtc, crtc_state, i) {
+		if (!crtc_state->active)
+			continue;
+		crtc_state->psr_transition = true;
+	}
+
+	state->acquire_ctx = &ctx;
+	drm_connector_list_iter_begin(psr_state->dev, &conn_iter);
+	drm_for_each_connector_iter(conn, &conn_iter) {
+		if (!conn->psr_capable)
+			continue;
+
+		conn_state = drm_atomic_get_connector_state(state, conn);
+		if (IS_ERR(conn_state)) {
+			ret = PTR_ERR(conn_state);
+			drm_connector_list_iter_end(&conn_iter);
+			goto out_free_state;
+		}
+
+		if (!conn_state->crtc)
+			continue;
+
+		crtc_state = drm_atomic_get_crtc_state(state, conn_state->crtc);
+		if (IS_ERR(crtc_state)) {
+			ret = PTR_ERR(crtc_state);
+			drm_connector_list_iter_end(&conn_iter);
+			goto out_free_state;
+		}
+
+		/* Don't use PSR if the crtc is driving multiple connectors */
+		if (hweight_long(crtc_state->connector_mask) > 1)
+			continue;
+
+		commit = true;
+		crtc_state->active = false;
+		crtc_state->psr_transition = true;
+		conn_state->psr_transition = true;
+	}
+	drm_connector_list_iter_end(&conn_iter);
+
+	/* Nothing to commit, so just exit */
+	if (!commit)
+		goto out_free_state;
+
+	ret = drm_atomic_commit(state);
+	if (ret)
+		goto out_free_state;
+
+	psr_state->save_state = save_state;
+	goto out;
+
+out_free_state:
+	drm_atomic_state_put(save_state);
+	drm_atomic_state_put(state);
+out:
+	DRM_MODESET_LOCK_ALL_END(ctx, ret);
+}
+
+static int
+drm_psr_helper_acquire_modeset_locks(struct drm_atomic_state *state,
+				     struct drm_modeset_acquire_ctx *ctx)
+{
+	struct drm_mode_config *config = &state->dev->mode_config;
+	struct drm_crtc *crtc;
+	struct drm_crtc_state *new_crtc_state;
+	struct drm_plane *plane;
+	struct drm_plane_state *new_plane_state;
+	int ret, i;
+
+	ret = drm_modeset_lock(&config->connection_mutex, ctx);
+	if (ret)
+		return ret;
+
+	for_each_new_plane_in_state(state, plane, new_plane_state, i) {
+		ret = drm_modeset_lock(&plane->mutex, ctx);
+		if (ret)
+			return ret;
+	}
+
+	for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
+		ret = drm_modeset_lock(&crtc->mutex, ctx);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+/**
+ * drm_psr_helper_flush - Restore the hardware to pre-PSR state
+ * @dev: DRM device
+ * @ctx: An acquire context to use for restoring the state
+ *
+ * This function should be called before every drm_atomic_commit to ensure any
+ * connectors that are currently self-refreshing revert back to being bus
+ * driven. Drivers may call this function outside of the atomic hooks if
+ * they wish to disable PSR pre-emptively (such as upon an input event or when
+ * GPU becomes active).
+ *
+ * If everything is successful, this function will schedule the PSR entry worker
+ * to enable PSR after the driver-specified timeout.
+ *
+ * If the PSR helper is not being used, this is a no-op.
+ */
+int drm_psr_helper_flush(struct drm_device *dev,
+			 struct drm_modeset_acquire_ctx *ctx)
+{
+	struct drm_mode_config *config = &dev->mode_config;
+	struct drm_psr_state *psr_state = config->psr_state;
+	int ret;
+
+	if (!psr_state)
+		return 0;
+
+	ret = drm_modeset_lock(&psr_state->mutex, ctx);
+	if (ret)
+		return ret;
+
+	if (!psr_state->save_state)
+		goto out;
+
+	ret = drm_psr_helper_acquire_modeset_locks(psr_state->save_state, ctx);
+	if (ret)
+		goto out;
+
+	ret = drm_atomic_helper_commit_duplicated_state(psr_state->save_state,
+							ctx);
+
+out:
+	psr_state->save_state = NULL;
+	if (!ret) {
+		mod_delayed_work(system_wq, &psr_state->entry_work,
+				 msecs_to_jiffies(psr_state->entry_delay_ms));
+	}
+	return ret;
+}
+EXPORT_SYMBOL(drm_psr_helper_flush);
+
+/**
+ * drm_psr_helper_register - Registers a connector with the PSR helpers
+ * @connector: the connector which has a PSR-supported display attached
+ *
+ * Note that this can be called once on initialization for fixed panels, or
+ * during enable/hotplug.
+ */
+int drm_psr_helper_register(struct drm_connector *connector)
+{
+	struct drm_mode_config *config = &connector->dev->mode_config;
+	struct drm_psr_state *psr_state = config->psr_state;
+
+	/* PSR helpers are uninitialized */
+	if (WARN_ON(!psr_state))
+		return -EINVAL;
+
+	/* Acquired via psr_helper_flush */
+	if (!drm_modeset_is_locked(&psr_state->mutex))
+		return -EINVAL;
+
+	connector->psr_capable = true;
+	return 0;
+}
+EXPORT_SYMBOL(drm_psr_helper_register);
+
+/**
+ * drm_psr_helper_unregister - Unregisters a connector with the PSR helpers
+ * @connector: the connector to unregister
+ */
+int drm_psr_helper_unregister(struct drm_connector *connector)
+{
+	struct drm_mode_config *config = &connector->dev->mode_config;
+	struct drm_psr_state *psr_state = config->psr_state;
+
+	/* PSR helpers are uninitialized */
+	if (WARN_ON(!psr_state))
+		return -EINVAL;
+
+	/* Acquired via psr_helper_flush */
+	if (!drm_modeset_is_locked(&psr_state->mutex))
+		return -EINVAL;
+
+	connector->psr_capable = false;
+	return 0;
+}
+EXPORT_SYMBOL(drm_psr_helper_unregister);
+
+/**
+ * drm_psr_helper_init - Initializes the PSR helpers
+ * @dev: DRM device
+ * @entry_delay_ms: The amount of time to wait after an atomic commit before
+ *		    activating PSR
+ *
+ * Drivers using the PSR helpers must call this some time after mode_config
+ * is initialized in order to make use of the PSR helpers. Typically
+ * entry_delay_ms is a function of how quickly the hardware can enter/exit PSR.
+ */
+int drm_psr_helper_init(struct drm_device *dev, unsigned int entry_delay_ms)
+{
+	struct drm_mode_config *config = &dev->mode_config;
+	struct drm_psr_state *psr_state;
+
+	psr_state = kzalloc(sizeof(*psr_state), GFP_KERNEL);
+	if (!psr_state)
+		return -ENOMEM;
+
+	INIT_DELAYED_WORK(&psr_state->entry_work, drm_psr_helper_entry_work);
+	drm_modeset_lock_init(&psr_state->mutex);
+	psr_state->dev = dev;
+	psr_state->entry_delay_ms = entry_delay_ms;
+
+	config->psr_state = psr_state;
+	return 0;
+}
+EXPORT_SYMBOL(drm_psr_helper_init);
+
+/**
+ * drm_psr_helper_cleanup - De-initializes the PSR helpers
+ * @dev: DRM device
+ */
+void drm_psr_helper_cleanup(struct drm_device *dev)
+{
+	struct drm_mode_config *config = &dev->mode_config;
+
+	cancel_delayed_work_sync(&config->psr_state->entry_work);
+	kfree(config->psr_state);
+	config->psr_state = NULL;
+}
+EXPORT_SYMBOL(drm_psr_helper_cleanup);
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index c8061992d6cb..9612b3d56f30 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -501,6 +501,17 @@ struct drm_connector_state {
 	/** @tv: TV connector state */
 	struct drm_tv_connector_state tv;
 
+	/**
+	 * @psr_transition:
+	 *
+	 * Used by the PSR helpers to denote when a PSR transition is occuring.
+	 * If your connector is PSR-capable, register it with the helpers and
+	 * check this flag in .enable() and .disable(). If it is true, instead
+	 * of shutting off the panel, put it in or take it out of self
+	 * refreshing mode.
+	 */
+	bool psr_transition;
+
 	/**
 	 * @picture_aspect_ratio: Connector property to control the
 	 * HDMI infoframe aspect ratio setting.
@@ -993,6 +1004,17 @@ struct drm_connector {
 	 */
 	struct drm_display_info display_info;
 
+	/**
+	 * @psr_capable:
+	 *
+	 * Set by the driver via drm_psr_helper_register(). Signals that this
+	 * connector (and associated pipe) is PSR capable and should be put in
+	 * low-power mode when it is inactive.
+	 *
+	 * Protected by &drm_mode_config.psr_state.mutex
+	 */
+	bool psr_capable;
+
 	/** @funcs: connector control functions */
 	const struct drm_connector_funcs *funcs;
 
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index f7c3022dbdf4..10acd4fc0991 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -299,6 +299,17 @@ struct drm_crtc_state {
 	 */
 	bool vrr_enabled;
 
+	/**
+	 * @psr_transition:
+	 *
+	 * Used by the PSR helpers to denote when a PSR transition is occuring.
+	 * This will be set on enable/disable callbacks when PSR is being
+	 * enabled or disabled. In some cases, it may not be desirable to fully
+	 * shut off the crtc during PSR. CRTC's can inspect this flag and
+	 * determine the best course of action.
+	 */
+	bool psr_transition;
+
 	/**
 	 * @event:
 	 *
diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
index 7f60e8eb269a..371b80d090ab 100644
--- a/include/drm/drm_mode_config.h
+++ b/include/drm/drm_mode_config.h
@@ -37,6 +37,7 @@ struct drm_atomic_state;
 struct drm_mode_fb_cmd2;
 struct drm_format_info;
 struct drm_display_mode;
+struct drm_psr_state;
 
 /**
  * struct drm_mode_config_funcs - basic driver provided mode setting functions
@@ -900,6 +901,11 @@ struct drm_mode_config {
 	 */
 	struct drm_atomic_state *suspend_state;
 
+	/**
+	 * @psr_state: Holds the state for the psr helper
+	 */
+	struct drm_psr_state *psr_state;
+
 	const struct drm_mode_config_helper_funcs *helper_private;
 };
 
diff --git a/include/drm/drm_psr_helper.h b/include/drm/drm_psr_helper.h
new file mode 100644
index 000000000000..972c4ec98d05
--- /dev/null
+++ b/include/drm/drm_psr_helper.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright (C) 2019 Google, Inc.
+ *
+ * Authors:
+ * Sean Paul <seanpaul@chromium.org>
+ */
+#ifndef DRM_PSR_HELPER_H_
+#define DRM_PSR_HELPER_H_
+
+struct drm_connector;
+struct drm_device;
+struct drm_psr_state;
+struct drm_modeset_acquire_ctx;
+
+int drm_psr_helper_flush(struct drm_device *dev,
+			 struct drm_modeset_acquire_ctx *ctx);
+
+int drm_psr_helper_register(struct drm_connector *connector);
+int drm_psr_helper_unregister(struct drm_connector *connector);
+
+int drm_psr_helper_init(struct drm_device *dev, unsigned int entry_delay_ms);
+void drm_psr_helper_cleanup(struct drm_device *dev);
+#endif
