[PATCH 02/10] drm: Add backlight helper

Noralf Trønnes
2020-04-29T12:57:58Z

This adds a function that creates a backlight device for a connector.
It does not deal with the KMS backlight ABI proposition[1] to add a
connector property. It only takes the current best practise to standardise
the creation of a backlight device for DRM drivers while we wait for the
property.

The brightness value is set using a connector state variable and an atomic
commit.

I have looked through some of the backlight users and this is what I've found:

GNOME [2]
---------

Brightness range: 0-100
Scale: Assumes perceptual

Avoids setting the sysfs brightness value to zero if max_brightness >= 99.
Can connect connector and backlight using the sysfs device.

KDE [3]
-------

Brightness range: 0-100
Scale: Assumes perceptual

Weston [4]
----------

Brightness range: 0-255
Scale: Assumes perceptual

Chromium OS [5]
---------------

Brightness range: 0-100
Scale: Depends on the sysfs file 'scale' which is a recent addition (2019)

xserver [6]
-----------

Brightness range: 0-x (driver specific) (1 is minimum, 0 is OFF)
Scale: Assumes perceptual

The builtin modesetting driver[7] does not support Backlight, Intel[8] does.

[1] https://lore.kernel.org/dri-devel/4b17ba08-39f3-57dd-5aad-d37d844b02c6@linux.intel.com/
[2] https://gitlab.gnome.org/GNOME/gnome-settings-daemon/-/blob/master/plugins/power/gsd-backlight.c
[3] https://github.com/KDE/powerdevil/blob/master/daemon/backends/upower/backlighthelper.cpp
[4] https://gitlab.freedesktop.org/wayland/weston/-/blob/master/libweston/backend-drm/drm.c
[5] https://chromium.googlesource.com/chromiumos/platform2/+/refs/heads/master/power_manager/powerd/system/internal_backlight.cc
[6] https://github.com/freedesktop/xorg-randrproto/blob/master/randrproto.txt
[7] https://gitlab.freedesktop.org/xorg/xserver/-/blob/master/hw/xfree86/drivers/modesetting/drmmode_display.c
[8] https://gitlab.freedesktop.org/xorg/driver/xf86-video-intel/-/blob/master/src/backlight.c

Cc: Hans de Goede <hdegoede@redhat.com>
Cc: Jani Nikula <jani.nikula@linux.intel.com>
Cc: Martin Peres <martin.peres@linux.intel.com>
Cc: Daniel Thompson <daniel.thompson@linaro.org>
Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
---
Documentation/gpu/drm-kms-helpers.rst | 6 +
drivers/gpu/drm/Kconfig | 7 ++
drivers/gpu/drm/Makefile | 1 +
drivers/gpu/drm/drm_backlight_helper.c | 154 +++++++++++++++++++++++++
include/drm/drm_backlight_helper.h | 9 ++
include/drm/drm_connector.h | 10 ++
6 files changed, 187 insertions(+)
create mode 100644 drivers/gpu/drm/drm_backlight_helper.c
create mode 100644 include/drm/drm_backlight_helper.h

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