diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
index 9668a7fe2408..29a2f4b49fd2 100644
--- a/Documentation/gpu/drm-kms-helpers.rst
+++ b/Documentation/gpu/drm-kms-helpers.rst
@@ -411,3 +411,9 @@ SHMEM GEM Helper Reference
 
 .. kernel-doc:: drivers/gpu/drm/drm_gem_shmem_helper.c
    :export:
+
+Backlight Helper Reference
+==========================
+
+.. kernel-doc:: drivers/gpu/drm/drm_backlight_helper.c
+   :export:
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index d0aa6cff2e02..f6e13e18c9ca 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -224,6 +224,13 @@ config DRM_GEM_SHMEM_HELPER
 	help
 	  Choose this if you need the GEM shmem helper functions
 
+config DRM_BACKLIGHT_HELPER
+	bool
+	depends on DRM
+	select BACKLIGHT_CLASS_DEVICE
+	help
+	  Choose this if you need the backlight device helper functions
+
 config DRM_VM
 	bool
 	depends on DRM && MMU
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 6493088a0fdd..0d17662dde0a 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -52,6 +52,7 @@ drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o
 drm_kms_helper-$(CONFIG_DRM_KMS_CMA_HELPER) += drm_fb_cma_helper.o
 drm_kms_helper-$(CONFIG_DRM_DP_AUX_CHARDEV) += drm_dp_aux_dev.o
 drm_kms_helper-$(CONFIG_DRM_DP_CEC) += drm_dp_cec.o
+drm_kms_helper-$(CONFIG_DRM_BACKLIGHT_HELPER) += drm_backlight_helper.o
 
 obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
 obj-$(CONFIG_DRM_DEBUG_SELFTEST) += selftests/
diff --git a/drivers/gpu/drm/drm_backlight_helper.c b/drivers/gpu/drm/drm_backlight_helper.c
new file mode 100644
index 000000000000..06e6a75d1d0a
--- /dev/null
+++ b/drivers/gpu/drm/drm_backlight_helper.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#include <linux/backlight.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_file.h>
+
+static int drm_backlight_update_status(struct backlight_device *bd)
+{
+	struct drm_connector *connector = bl_get_data(bd);
+	struct drm_connector_state *connector_state;
+	struct drm_device *dev = connector->dev;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_atomic_state *state;
+	int ret;
+
+	state = drm_atomic_state_alloc(dev);
+	if (!state)
+		return -ENOMEM;
+
+	drm_modeset_acquire_init(&ctx, 0);
+	state->acquire_ctx = &ctx;
+retry:
+	connector_state = drm_atomic_get_connector_state(state, connector);
+	if (IS_ERR(connector_state)) {
+		ret = PTR_ERR(connector_state);
+		goto out;
+	}
+
+	connector_state->backlight_brightness = bd->props.brightness;
+
+	ret = drm_atomic_commit(state);
+out:
+	if (ret == -EDEADLK) {
+		drm_atomic_state_clear(state);
+		drm_modeset_backoff(&ctx);
+		goto retry;
+	}
+
+	drm_atomic_state_put(state);
+
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+
+	return ret;
+}
+
+static int drm_backlight_get_brightness(struct backlight_device *bd)
+{
+	struct drm_connector *connector = bl_get_data(bd);
+	struct drm_device *dev = connector->dev;
+	int brightness;
+
+	drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
+	brightness = connector->state->backlight_brightness;
+	drm_modeset_unlock(&dev->mode_config.connection_mutex);
+
+	return brightness;
+}
+
+static const struct backlight_ops drm_backlight_ops = {
+	.get_brightness = drm_backlight_get_brightness,
+	.update_status	= drm_backlight_update_status,
+};
+
+/* Can be exported for drivers carrying a legacy name */
+static int drm_backlight_register_with_name(struct drm_connector *connector, const char *name)
+{
+	struct backlight_device *bd;
+	const struct backlight_properties props = {
+		.type = BACKLIGHT_RAW,
+		.scale = BACKLIGHT_SCALE_NON_LINEAR,
+		.max_brightness = 100,
+	};
+
+	if (WARN_ON(!drm_core_check_feature(connector->dev, DRIVER_MODESET) ||
+		    !drm_drv_uses_atomic_modeset(connector->dev) ||
+		    !connector->kdev))
+		return -EINVAL;
+
+	bd = backlight_device_register(name, connector->kdev, connector,
+				       &drm_backlight_ops, &props);
+	if (IS_ERR(bd))
+		return PTR_ERR(bd);
+
+	connector->backlight = bd;
+
+	return 0;
+}
+
+/**
+ * drm_backlight_register() - Register a backlight device for a connector
+ * @connector: Connector
+ *
+ * This function registers a backlight device for @connector with the following
+ * characteristics:
+ *
+ * - The connector sysfs device is used as a parent device for the backlight device.
+ *   Userspace can use this to connect backlight device and connector.
+ * - Name will be on the form: **card0-HDMI-A-1-backlight**
+ * - Type is "raw"
+ * - Scale is "non-linear" (perceptual)
+ * - Max brightness is 100 giving a range of 0-100 inclusive
+ * - Reading sysfs **brightness** returns the backlight device property
+ * - Reading sysfs **actual_brightness** returns the connector state value
+ * - Writing sysfs **bl_power** is ignored, the DPMS connector property should
+ *   be used to control power.
+ * - Backlight device suspend/resume events are ignored.
+ *
+ * Note:
+ *
+ * Brightness zero should not turn off backlight it should be the minimum
+ * brightness, DPMS handles power.
+ *
+ * This function must be called from &drm_connector_funcs->late_register() since
+ * it depends on the sysfs device.
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_backlight_register(struct drm_connector *connector)
+{
+	const char *name = NULL;
+	int ret;
+
+	name = kasprintf(GFP_KERNEL, "card%d-%s-backlight",
+			 connector->dev->primary->index, connector->name);
+	if (!name)
+		return -ENOMEM;
+
+	ret = drm_backlight_register_with_name(connector, name);
+	kfree(name);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_backlight_register);
+
+/**
+ * drm_backlight_unregister() - Unregister backlight device
+ * @connector: Connector
+ *
+ * Unregister a backlight device. This must be called from the
+ * &drm_connector_funcs->early_unregister() callback.
+ */
+void drm_backlight_unregister(struct drm_connector *connector)
+{
+	backlight_device_unregister(connector->backlight);
+}
+EXPORT_SYMBOL(drm_backlight_unregister);
diff --git a/include/drm/drm_backlight_helper.h b/include/drm/drm_backlight_helper.h
new file mode 100644
index 000000000000..4151b66eb0b4
--- /dev/null
+++ b/include/drm/drm_backlight_helper.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
+
+#ifndef _LINUX_DRM_BACKLIGHT_HELPER_H
+#define _LINUX_DRM_BACKLIGHT_HELPER_H
+
+int drm_backlight_register(struct drm_connector *connector);
+void drm_backlight_unregister(struct drm_connector *connector);
+
+#endif
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 221910948b37..ce678b694f45 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -32,6 +32,7 @@
 
 #include <uapi/drm/drm_mode.h>
 
+struct backlight_device;
 struct drm_connector_helper_funcs;
 struct drm_modeset_acquire_ctx;
 struct drm_device;
@@ -656,6 +657,12 @@ struct drm_connector_state {
 	 */
 	u8 max_bpc;
 
+	/**
+	 * @backlight_brightness: Brightness value of the connector backlight
+	 * device. See drm_backlight_register().
+	 */
+	u8 backlight_brightness;
+
 	/**
 	 * @hdr_output_metadata:
 	 * DRM blob property for HDR output metadata
@@ -1422,6 +1429,9 @@ struct drm_connector {
 
 	/** @hdr_sink_metadata: HDR Metadata Information read from sink */
 	struct hdr_sink_metadata hdr_sink_metadata;
+
+	/** @backlight: Backlight device. See drm_backlight_register() */
+	struct backlight_device *backlight;
 };
 
 #define obj_to_connector(x) container_of(x, struct drm_connector, base)
