@@ -12,6 +12,7 @@ menuconfig DRM
select I2C
select I2C_ALGOBIT
select DMA_SHARED_BUFFER
+ select BACKLIGHT_CLASS_DEVICE
help
Kernel-level support for the Direct Rendering Infrastructure (DRI)
introduced in XFree86 4.0. If you say Y here, you need to select
@@ -15,7 +15,7 @@ drm-y := drm_auth.o drm_bufs.o drm_cache.o \
drm_modeset_lock.o drm_atomic.o drm_bridge.o \
drm_framebuffer.o drm_connector.o drm_blend.o \
drm_encoder.o drm_mode_object.o drm_property.o \
- drm_plane.o drm_color_mgmt.o
+ drm_plane.o drm_color_mgmt.o drm_backlight.o
drm-$(CONFIG_COMPAT) += drm_ioc32.o
drm-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_gem_cma_helper.o
new file mode 100644
@@ -0,0 +1,387 @@
+/*
+ * DRM Backlight Helpers
+ * Copyright (c) 2014 David Herrmann
+ */
+
+#include <linux/backlight.h>
+#include <linux/fs.h>
+#include <linux/list.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <drm/drmP.h>
+#include <drm/drm_backlight.h>
+
+/**
+ * DOC: Backlight Devices
+ *
+ * Backlight devices have always been managed as a separate subsystem,
+ * independent of DRM. They are usually controlled via separate hardware
+ * interfaces than the display controller, so the split works out fine.
+ * However, backlight brightness is a property of a display, and thus a
+ * property of a DRM connector. We already manage DPMS states via connector
+ * properties, so it is natural to keep brightness control at the same place.
+ *
+ * This DRM backlight interface implements generic backlight properties on
+ * connectors. It does not handle any hardware backends but simply forwards
+ * the requests to an available and linked backlight device. The links between
+ * connectors and backlight devices have to be established by DRM drivers and
+ * can be modified by user-space via sysfs (and udev rules). The name of the
+ * backlight device can be written to a sysfs attribute called 'backlight'.
+ * The device is looked up and linked to the connector (replacing a possible
+ * previous backlight device). A 'change' uevent is sent whenever a link is
+ * modified.
+ *
+ * Drivers have to call drm_backlight_alloc() after allocating a connector via
+ * drm_connector_init(). This will automatically add a backlight device to the
+ * given connector. No hardware device is linked to the connector by default.
+ * Drivers can set up a default device via drm_backlight_set_name(), but are
+ * free to leave it empty. User-space will then have to set up the link.
+ */
+
+struct drm_backlight {
+ struct list_head list;
+ struct drm_connector *connector;
+ char *link_name;
+ struct backlight_device *link;
+ struct work_struct work;
+ unsigned int set_value;
+ bool changed : 1;
+};
+
+static LIST_HEAD(drm_backlight_list);
+static DEFINE_SPINLOCK(drm_backlight_lock);
+
+/* caller must hold @drm_backlight_lock */
+static bool __drm_backlight_is_registered(struct drm_backlight *b)
+{
+ /* a device is live if it is linked to @drm_backlight_list */
+ return !list_empty(&b->list);
+}
+
+/* caller must hold @drm_backlight_lock */
+static void __drm_backlight_schedule(struct drm_backlight *b)
+{
+ if (__drm_backlight_is_registered(b))
+ schedule_work(&b->work);
+}
+
+static void __drm_backlight_worker(struct work_struct *w)
+{
+ struct drm_backlight *b = container_of(w, struct drm_backlight, work);
+ static char *ep[] = { "BACKLIGHT=1", NULL };
+ struct backlight_device *bd;
+ bool send_uevent;
+ unsigned int v;
+
+ spin_lock(&drm_backlight_lock);
+ send_uevent = b->changed;
+ b->changed = false;
+ v = b->set_value;
+ bd = b->link;
+ backlight_device_ref(bd);
+ spin_unlock(&drm_backlight_lock);
+
+ if (bd) {
+ backlight_set_brightness(bd, v, BACKLIGHT_UPDATE_DRM);
+ backlight_device_unref(bd);
+ }
+
+ if (send_uevent)
+ kobject_uevent_env(&b->connector->kdev->kobj, KOBJ_CHANGE, ep);
+}
+
+/* caller must hold @drm_backlight_lock */
+static void __drm_backlight_prop_changed(struct drm_backlight *b, uint64_t v)
+{
+ uint64_t max;
+
+ if (!b->link)
+ return;
+
+ max = b->link->props.max_brightness;
+ if (v >= U16_MAX)
+ b->set_value = max;
+ else
+ b->set_value = (v * max) >> 16;
+ __drm_backlight_schedule(b);
+}
+
+/* caller must hold @drm_backlight_lock */
+static void __drm_backlight_real_changed(struct drm_backlight *b, uint64_t v)
+{
+ struct drm_mode_config *config = &b->connector->dev->mode_config;
+ unsigned int max, set;
+
+ if (!b->link)
+ return;
+
+ set = v;
+ max = b->link->props.max_brightness;
+ if (max < 1)
+ return;
+
+ if (set >= max)
+ set = U16_MAX;
+ else if (max <= U16_MAX)
+ set = (set << 16) / max;
+ else
+ set = div_u64(v << 16, max);
+
+ drm_object_property_set_value(&b->connector->base,
+ config->brightness_property, v);
+}
+
+/* caller must hold @drm_backlight_lock */
+static void __drm_backlight_link(struct drm_backlight *b,
+ struct backlight_device *bd)
+{
+ if (bd != b->link) {
+ backlight_device_unref(b->link);
+ b->link = bd;
+ backlight_device_ref(b->link);
+ if (bd)
+ __drm_backlight_real_changed(b, bd->props.brightness);
+ b->changed = true;
+ __drm_backlight_schedule(b);
+ }
+}
+
+/* caller must hold @drm_backlight_lock */
+static void __drm_backlight_lookup(struct drm_backlight *b)
+{
+ struct backlight_device *bd;
+
+ if (b->link_name)
+ bd = backlight_device_lookup(b->link_name);
+ else
+ bd = NULL;
+
+ __drm_backlight_link(b, bd);
+ backlight_device_unref(bd);
+}
+
+/**
+ * drm_backlight_alloc - add backlight capability to a connector
+ * @connector: connector to add backlight to
+ *
+ * This allocates a new DRM-backlight device and links it to @connector. This
+ * *must* be called before registering the connector. The backlight device will
+ * be automatically registered in sync with the connector. It will also get
+ * removed once the connector is removed.
+ *
+ * The connector will not have any hardware backlight linked by default. You
+ * need to call drm_backlight_set_name() if you want to set a default
+ * backlight. User-space can overwrite those via sysfs.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int drm_backlight_alloc(struct drm_connector *connector)
+{
+ struct drm_mode_config *config = &connector->dev->mode_config;
+ struct drm_backlight *b;
+
+ b = kzalloc(sizeof(*b), GFP_KERNEL);
+ if (!b)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&b->list);
+ INIT_WORK(&b->work, __drm_backlight_worker);
+ b->connector = connector;
+ connector->backlight = b;
+
+ drm_object_attach_property(&connector->base,
+ config->brightness_property, U16_MAX);
+
+ return 0;
+}
+EXPORT_SYMBOL(drm_backlight_alloc);
+
+void drm_backlight_free(struct drm_connector *connector)
+{
+ struct drm_backlight *b = connector->backlight;
+
+ if (b) {
+ WARN_ON(__drm_backlight_is_registered(b));
+ WARN_ON(b->link);
+
+ kfree(b->link_name);
+ kfree(b);
+ connector->backlight = NULL;
+ }
+}
+
+void drm_backlight_register(struct drm_backlight *b)
+{
+ if (!b)
+ return;
+
+ WARN_ON(__drm_backlight_is_registered(b));
+
+ spin_lock(&drm_backlight_lock);
+ list_add(&b->list, &drm_backlight_list);
+ __drm_backlight_lookup(b);
+ spin_unlock(&drm_backlight_lock);
+}
+
+void drm_backlight_unregister(struct drm_backlight *b)
+{
+ if (!b)
+ return;
+
+ WARN_ON(!__drm_backlight_is_registered(b));
+
+ spin_lock(&drm_backlight_lock);
+ list_del_init(&b->list);
+ __drm_backlight_link(b, NULL);
+ spin_unlock(&drm_backlight_lock);
+
+ cancel_work_sync(&b->work);
+}
+
+/**
+ * drm_backlight_get_name - retrieve name of linked backlight device
+ * @b: DRM backlight to retrieve name of
+ * @buf: target buffer for name
+ * @max: size of the target buffer
+ *
+ * This retrieves the name of the backlight device linked to @b and writes it
+ * into @buf. If @buf is NULL or @max is 0, no name will be retrieved, but this
+ * function only tests whether a link is set.
+ * Otherwise, the name will always be written into @buf and will always be
+ * zero-terminated (truncated if too long).
+ *
+ * If no backlight device is linked to @b, this returns -ENOENT. Otherwise, the
+ * length of the written name (excluding the terminating 0 character) is
+ * returned.
+ * Note that if a device name has been set but the underlying backlight device
+ * does not exist, this will still return the linked name. -ENOENT is only
+ * returned if no device name has been set, yet (or has been cleared).
+ *
+ * Returns: On success the length of the written name, on failure a negative
+ * error code.
+ */
+int drm_backlight_get_name(struct drm_backlight *b, char *buf, size_t max)
+{
+ int r;
+
+ spin_lock(&drm_backlight_lock);
+
+ if (!b->link_name) {
+ r = -ENOENT;
+ goto unlock;
+ }
+
+ r = 0;
+ if (buf && max > 0) {
+ r = strlen(b->link_name);
+ if (r + 1 > max)
+ r = max - 1;
+ buf[r] = 0;
+ memcpy(buf, b->link_name, r);
+ }
+
+unlock:
+ spin_unlock(&drm_backlight_lock);
+ return r;
+}
+EXPORT_SYMBOL(drm_backlight_get_name);
+
+/**
+ * drm_backlight_set_name - Change the device link of a DRM backlight
+ * @b: DRM backlight to modify
+ * @name: name of backlight device
+ *
+ * This changes the backlight device-link on @b to the hardware device with
+ * name @name. @name is stored on the backlight device, even if no such
+ * hardware device is registered, yet. If a backlight device appears later on,
+ * it will be automatically linked to all matching DRM backlight devices. If a
+ * real hardware backlight device already exists with such a name, it is linked
+ * with immediate effect.
+ *
+ * Whenever a real hardware backlight is linked or unlinked from a DRM connector
+ * an uevent with "BACKLIGHT=1" is generated on the connector.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int drm_backlight_set_name(struct drm_backlight *b, const char *name)
+{
+ char *namecopy;
+
+ if (name && *name) {
+ namecopy = kstrdup(name, GFP_KERNEL);
+ if (!namecopy)
+ return -ENOMEM;
+ } else {
+ namecopy = NULL;
+ }
+
+ spin_lock(&drm_backlight_lock);
+
+ kfree(b->link_name);
+ b->link_name = namecopy;
+ if (__drm_backlight_is_registered(b))
+ __drm_backlight_lookup(b);
+
+ spin_unlock(&drm_backlight_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(drm_backlight_set_name);
+
+void drm_backlight_set_brightness(struct drm_backlight *b, uint64_t value)
+{
+ spin_lock(&drm_backlight_lock);
+ __drm_backlight_prop_changed(b, value);
+ spin_unlock(&drm_backlight_lock);
+}
+
+static int drm_backlight_notify(struct notifier_block *self,
+ unsigned long event, void *data)
+{
+ struct backlight_device *bd = data;
+ struct drm_backlight *b;
+ const char *name;
+
+ spin_lock(&drm_backlight_lock);
+
+ switch (event) {
+ case BACKLIGHT_REGISTERED:
+ name = dev_name(&bd->dev);
+ if (!name)
+ break;
+
+ list_for_each_entry(b, &drm_backlight_list, list)
+ if (!b->link && !strcmp(name, b->link_name))
+ __drm_backlight_link(b, bd);
+
+ break;
+ case BACKLIGHT_UNREGISTERED:
+ list_for_each_entry(b, &drm_backlight_list, list)
+ if (b->link == bd)
+ __drm_backlight_link(b, NULL);
+
+ break;
+ }
+
+ spin_unlock(&drm_backlight_lock);
+
+ return 0;
+}
+
+static struct notifier_block drm_backlight_notifier = {
+ .notifier_call = drm_backlight_notify,
+};
+
+int drm_backlight_init(void)
+{
+ return backlight_register_notifier(&drm_backlight_notifier);
+}
+
+void drm_backlight_exit(void)
+{
+ backlight_unregister_notifier(&drm_backlight_notifier);
+}
@@ -23,6 +23,7 @@
#include <drm/drmP.h>
#include <drm/drm_connector.h>
#include <drm/drm_edid.h>
+#include <drm/drm_backlight.h>
#include "drm_crtc_internal.h"
#include "drm_internal.h"
@@ -324,6 +325,7 @@ void drm_connector_cleanup(struct drm_connector *connector)
struct drm_device *dev = connector->dev;
struct drm_display_mode *mode, *t;
+ drm_backlight_free(connector);
/* The connector should have been removed from userspace long before
* it is finally destroyed.
*/
@@ -379,6 +381,7 @@ int drm_connector_register(struct drm_connector *connector)
if (connector->registered)
return 0;
+ drm_backlight_register(connector->backlight);
ret = drm_sysfs_connector_add(connector);
if (ret)
return ret;
@@ -415,6 +418,8 @@ EXPORT_SYMBOL(drm_connector_register);
*/
void drm_connector_unregister(struct drm_connector *connector)
{
+ drm_backlight_unregister(connector->backlight);
+
if (!connector->registered)
return;
@@ -957,10 +962,16 @@ int drm_mode_connector_set_obj_prop(struct drm_mode_object *obj,
{
int ret = -EINVAL;
struct drm_connector *connector = obj_to_connector(obj);
+ struct drm_mode_config *config = &connector->dev->mode_config;
/* Do DPMS ourselves */
if (property == connector->dev->mode_config.dpms_property) {
ret = (*connector->funcs->dpms)(connector, (int)value);
+ } else if (property == config->brightness_property) {
+ if (connector->backlight)
+ drm_backlight_set_brightness(connector->backlight,
+ value);
+ ret = 0;
} else if (connector->funcs->set_property)
ret = connector->funcs->set_property(connector, property, value);
@@ -41,6 +41,7 @@
#include <drm/drm_atomic.h>
#include <drm/drm_auth.h>
#include <drm/drm_debugfs_crc.h>
+#include <drm/drm_backlight.h>
#include "drm_crtc_internal.h"
#include "drm_internal.h"
@@ -451,6 +452,11 @@ static int drm_mode_create_standard_properties(struct drm_device *dev)
return -ENOMEM;
dev->mode_config.gamma_lut_size_property = prop;
+ prop = drm_property_create_range(dev, DRM_MODE_PROP_RANGE, "BRIGHTNESS", 0, U16_MAX);
+ if (!prop)
+ return -ENOMEM;
+ dev->mode_config.brightness_property = prop;
+
return 0;
}
@@ -33,6 +33,7 @@
#include <linux/mount.h>
#include <linux/slab.h>
#include <drm/drmP.h>
+#include <drm/drm_backlight.h>
#include "drm_crtc_internal.h"
#include "drm_legacy.h"
#include "drm_internal.h"
@@ -826,6 +827,7 @@ static const struct file_operations drm_stub_fops = {
static void drm_core_exit(void)
{
+ drm_backlight_exit();
unregister_chrdev(DRM_MAJOR, "drm");
debugfs_remove(drm_debugfs_root);
drm_sysfs_destroy();
@@ -855,6 +857,12 @@ static int __init drm_core_init(void)
goto error;
}
+ ret = drm_backlight_init();
+ if (ret < 0) {
+ DRM_ERROR("Cannot initialize backlight interface\n");
+ goto error;
+ }
+
ret = register_chrdev(DRM_MAJOR, "drm", &drm_stub_fops);
if (ret < 0)
goto error;
@@ -20,6 +20,7 @@
#include <drm/drm_sysfs.h>
#include <drm/drmP.h>
+#include <drm/drm_backlight.h>
#include "drm_internal.h"
#define to_drm_minor(d) dev_get_drvdata(d)
@@ -215,16 +216,69 @@ static ssize_t modes_show(struct device *device,
return written;
}
+static ssize_t backlight_show(struct device *device,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct drm_connector *connector = to_drm_connector(device);
+ int r;
+
+ if (!connector->backlight)
+ return -ENOTSUPP;
+
+ r = drm_backlight_get_name(connector->backlight, buf, PAGE_SIZE);
+ if (r < 0)
+ return r;
+
+ if (r + 1 < PAGE_SIZE) {
+ buf[r++] = '\n';
+ buf[r] = 0;
+ }
+
+ return r;
+}
+
+static ssize_t backlight_store(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct drm_connector *connector = to_drm_connector(device);
+ const char *t;
+ char *name;
+ size_t len;
+ int r;
+
+ if (!connector->backlight)
+ return -ENOTSUPP;
+
+ t = strchrnul(buf, '\n');
+ len = t - buf;
+
+ name = kstrndup(buf, len, GFP_TEMPORARY);
+ if (!name)
+ return -ENOMEM;
+
+ r = drm_backlight_set_name(connector->backlight, name);
+ kfree(name);
+
+ if (r < 0)
+ return r;
+
+ return len;
+}
+
static DEVICE_ATTR_RW(status);
static DEVICE_ATTR_RO(enabled);
static DEVICE_ATTR_RO(dpms);
static DEVICE_ATTR_RO(modes);
+static DEVICE_ATTR_RW(backlight);
static struct attribute *connector_dev_attrs[] = {
&dev_attr_status.attr,
&dev_attr_enabled.attr,
&dev_attr_dpms.attr,
&dev_attr_modes.attr,
+ &dev_attr_backlight.attr,
NULL
};
@@ -113,6 +113,9 @@ static void backlight_generate_event(struct backlight_device *bd,
case BACKLIGHT_UPDATE_HOTKEY:
envp[0] = "SOURCE=hotkey";
break;
+ case BACKLIGHT_UPDATE_DRM:
+ envp[0] = "SOURCE=drm";
+ break;
default:
envp[0] = "SOURCE=unknown";
break;
new file mode 100644
@@ -0,0 +1,44 @@
+#ifndef __DRM_BACKLIGHT_H__
+#define __DRM_BACKLIGHT_H__
+
+/*
+ * Copyright (c) 2014 David Herrmann <dh.herrmann at gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+
+struct drm_backlight;
+struct drm_connector;
+
+int drm_backlight_init(void);
+void drm_backlight_exit(void);
+
+int drm_backlight_alloc(struct drm_connector *connector);
+void drm_backlight_free(struct drm_connector *connector);
+void drm_backlight_register(struct drm_backlight *b);
+void drm_backlight_unregister(struct drm_backlight *b);
+
+int drm_backlight_get_name(struct drm_backlight *b, char *buf, size_t max);
+int drm_backlight_set_name(struct drm_backlight *b, const char *name);
+void drm_backlight_set_brightness(struct drm_backlight *b, uint64_t value);
+
+#endif /* __DRM_BACKLIGHT_H__ */
@@ -682,6 +682,9 @@ struct drm_connector {
uint8_t num_h_tile, num_v_tile;
uint8_t tile_h_loc, tile_v_loc;
uint16_t tile_h_size, tile_v_size;
+
+ /* backlight link */
+ struct drm_backlight *backlight;
};
#define obj_to_connector(x) container_of(x, struct drm_connector, base)
@@ -1140,6 +1140,11 @@ struct drm_mode_config {
*/
struct drm_property *dpms_property;
/**
+ * @brightness_property: Default connector property to control the
+ * connector's backlight brightness.
+ */
+ struct drm_property *brightness_property;
+ /**
* @path_property: Default connector property to hold the DP MST path
* for the port.
*/
@@ -31,6 +31,7 @@
enum backlight_update_reason {
BACKLIGHT_UPDATE_HOTKEY,
BACKLIGHT_UPDATE_SYSFS,
+ BACKLIGHT_UPDATE_DRM,
};
enum backlight_type {