summaryrefslogtreecommitdiff
path: root/drivers/video/backlight/backlight.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/backlight/backlight.c')
-rw-r--r--drivers/video/backlight/backlight.c434
1 files changed, 307 insertions, 127 deletions
diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c
index 3fccb6d3c8c3..1e9b7e85d99a 100644
--- a/drivers/video/backlight/backlight.c
+++ b/drivers/video/backlight/backlight.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Backlight Lowlevel Control Abstraction
*
@@ -14,73 +15,103 @@
#include <linux/notifier.h>
#include <linux/ctype.h>
#include <linux/err.h>
-#include <linux/fb.h>
#include <linux/slab.h>
+#include <linux/of.h>
#ifdef CONFIG_PMAC_BACKLIGHT
#include <asm/backlight.h>
#endif
+/**
+ * DOC: overview
+ *
+ * The backlight core supports implementing backlight drivers.
+ *
+ * A backlight driver registers a driver using
+ * devm_backlight_device_register(). The properties of the backlight
+ * driver such as type and max_brightness must be specified.
+ * When the core detect changes in for example brightness or power state
+ * the update_status() operation is called. The backlight driver shall
+ * implement this operation and use it to adjust backlight.
+ *
+ * Several sysfs attributes are provided by the backlight core::
+ *
+ * - brightness R/W, set the requested brightness level
+ * - actual_brightness RO, the brightness level used by the HW
+ * - max_brightness RO, the maximum brightness level supported
+ *
+ * See Documentation/ABI/stable/sysfs-class-backlight for the full list.
+ *
+ * The backlight can be adjusted using the sysfs interface, and
+ * the backlight driver may also support adjusting backlight using
+ * a hot-key or some other platform or firmware specific way.
+ *
+ * The driver must implement the get_brightness() operation if
+ * the HW do not support all the levels that can be specified in
+ * brightness, thus providing user-space access to the actual level
+ * via the actual_brightness attribute.
+ *
+ * When the backlight changes this is reported to user-space using
+ * an uevent connected to the actual_brightness attribute.
+ * When brightness is set by platform specific means, for example
+ * a hot-key to adjust backlight, the driver must notify the backlight
+ * core that brightness has changed using backlight_force_update().
+ *
+ * Display drives can control the backlight device's status using
+ * backlight_notify_blank() and backlight_notify_blank_all(). If this
+ * results in a change in the backlight state the functions call the
+ * update_status() operation.
+ */
+
+static struct list_head backlight_dev_list;
+static struct mutex backlight_dev_list_mutex;
+
static const char *const backlight_types[] = {
[BACKLIGHT_RAW] = "raw",
[BACKLIGHT_PLATFORM] = "platform",
[BACKLIGHT_FIRMWARE] = "firmware",
};
-#if defined(CONFIG_FB) || (defined(CONFIG_FB_MODULE) && \
- defined(CONFIG_BACKLIGHT_CLASS_DEVICE_MODULE))
-/* This callback gets called when something important happens inside a
- * framebuffer driver. We're looking if that important event is blanking,
- * and if it is, we're switching backlight power as well ...
- */
-static int fb_notifier_callback(struct notifier_block *self,
- unsigned long event, void *data)
+static const char *const backlight_scale_types[] = {
+ [BACKLIGHT_SCALE_UNKNOWN] = "unknown",
+ [BACKLIGHT_SCALE_LINEAR] = "linear",
+ [BACKLIGHT_SCALE_NON_LINEAR] = "non-linear",
+};
+
+void backlight_notify_blank(struct backlight_device *bd, struct device *display_dev,
+ bool fb_on, bool prev_fb_on)
{
- struct backlight_device *bd;
- struct fb_event *evdata = data;
+ guard(mutex)(&bd->ops_lock);
- /* If we aren't interested in this event, skip it immediately ... */
- if (event != FB_EVENT_BLANK && event != FB_EVENT_CONBLANK)
- return 0;
+ if (!bd->ops)
+ return;
+ if (bd->ops->controls_device && !bd->ops->controls_device(bd, display_dev))
+ return;
- bd = container_of(self, struct backlight_device, fb_notif);
- mutex_lock(&bd->ops_lock);
- if (bd->ops)
- if (!bd->ops->check_fb ||
- bd->ops->check_fb(bd, evdata->info)) {
- bd->props.fb_blank = *(int *)evdata->data;
- if (bd->props.fb_blank == FB_BLANK_UNBLANK)
- bd->props.state &= ~BL_CORE_FBBLANK;
- else
- bd->props.state |= BL_CORE_FBBLANK;
+ if (fb_on && (!prev_fb_on || !bd->use_count)) {
+ if (!bd->use_count++) {
+ bd->props.state &= ~BL_CORE_FBBLANK;
backlight_update_status(bd);
}
- mutex_unlock(&bd->ops_lock);
- return 0;
+ } else if (!fb_on && prev_fb_on && bd->use_count) {
+ if (!(--bd->use_count)) {
+ bd->props.state |= BL_CORE_FBBLANK;
+ backlight_update_status(bd);
+ }
+ }
}
+EXPORT_SYMBOL(backlight_notify_blank);
-static int backlight_register_fb(struct backlight_device *bd)
+void backlight_notify_blank_all(struct device *display_dev, bool fb_on, bool prev_fb_on)
{
- memset(&bd->fb_notif, 0, sizeof(bd->fb_notif));
- bd->fb_notif.notifier_call = fb_notifier_callback;
+ struct backlight_device *bd;
- return fb_register_client(&bd->fb_notif);
-}
+ guard(mutex)(&backlight_dev_list_mutex);
-static void backlight_unregister_fb(struct backlight_device *bd)
-{
- fb_unregister_client(&bd->fb_notif);
-}
-#else
-static inline int backlight_register_fb(struct backlight_device *bd)
-{
- return 0;
+ list_for_each_entry(bd, &backlight_dev_list, entry)
+ backlight_notify_blank(bd, display_dev, fb_on, prev_fb_on);
}
-
-static inline void backlight_unregister_fb(struct backlight_device *bd)
-{
-}
-#endif /* CONFIG_FB */
+EXPORT_SYMBOL(backlight_notify_blank_all);
static void backlight_generate_event(struct backlight_device *bd,
enum backlight_update_reason reason)
@@ -103,20 +134,20 @@ static void backlight_generate_event(struct backlight_device *bd,
sysfs_notify(&bd->dev.kobj, NULL, "actual_brightness");
}
-static ssize_t backlight_show_power(struct device *dev,
- struct device_attribute *attr, char *buf)
+static ssize_t bl_power_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
{
struct backlight_device *bd = to_backlight_device(dev);
return sprintf(buf, "%d\n", bd->props.power);
}
-static ssize_t backlight_store_power(struct device *dev,
- struct device_attribute *attr, const char *buf, size_t count)
+static ssize_t bl_power_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
{
int rc;
struct backlight_device *bd = to_backlight_device(dev);
- unsigned long power;
+ unsigned long power, old_power;
rc = kstrtoul(buf, 0, &power);
if (rc)
@@ -127,17 +158,24 @@ static ssize_t backlight_store_power(struct device *dev,
if (bd->ops) {
pr_debug("set power to %lu\n", power);
if (bd->props.power != power) {
+ old_power = bd->props.power;
bd->props.power = power;
- backlight_update_status(bd);
+ rc = backlight_update_status(bd);
+ if (rc)
+ bd->props.power = old_power;
+ else
+ rc = count;
+ } else {
+ rc = count;
}
- rc = count;
}
mutex_unlock(&bd->ops_lock);
return rc;
}
+static DEVICE_ATTR_RW(bl_power);
-static ssize_t backlight_show_brightness(struct device *dev,
+static ssize_t brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct backlight_device *bd = to_backlight_device(dev);
@@ -145,18 +183,10 @@ static ssize_t backlight_show_brightness(struct device *dev,
return sprintf(buf, "%d\n", bd->props.brightness);
}
-static ssize_t backlight_store_brightness(struct device *dev,
- struct device_attribute *attr, const char *buf, size_t count)
+int backlight_device_set_brightness(struct backlight_device *bd,
+ unsigned long brightness)
{
- int rc;
- struct backlight_device *bd = to_backlight_device(dev);
- unsigned long brightness;
-
- rc = kstrtoul(buf, 0, &brightness);
- if (rc)
- return rc;
-
- rc = -ENXIO;
+ int rc = -ENXIO;
mutex_lock(&bd->ops_lock);
if (bd->ops) {
@@ -165,8 +195,7 @@ static ssize_t backlight_store_brightness(struct device *dev,
else {
pr_debug("set brightness to %lu\n", brightness);
bd->props.brightness = brightness;
- backlight_update_status(bd);
- rc = count;
+ rc = backlight_update_status(bd);
}
}
mutex_unlock(&bd->ops_lock);
@@ -175,38 +204,74 @@ static ssize_t backlight_store_brightness(struct device *dev,
return rc;
}
+EXPORT_SYMBOL(backlight_device_set_brightness);
-static ssize_t backlight_show_type(struct device *dev,
- struct device_attribute *attr, char *buf)
+static ssize_t brightness_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ int rc;
+ struct backlight_device *bd = to_backlight_device(dev);
+ unsigned long brightness;
+
+ rc = kstrtoul(buf, 0, &brightness);
+ if (rc)
+ return rc;
+
+ rc = backlight_device_set_brightness(bd, brightness);
+
+ return rc ? rc : count;
+}
+static DEVICE_ATTR_RW(brightness);
+
+static ssize_t type_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
{
struct backlight_device *bd = to_backlight_device(dev);
return sprintf(buf, "%s\n", backlight_types[bd->props.type]);
}
+static DEVICE_ATTR_RO(type);
-static ssize_t backlight_show_max_brightness(struct device *dev,
+static ssize_t max_brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct backlight_device *bd = to_backlight_device(dev);
return sprintf(buf, "%d\n", bd->props.max_brightness);
}
+static DEVICE_ATTR_RO(max_brightness);
-static ssize_t backlight_show_actual_brightness(struct device *dev,
+static ssize_t actual_brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int rc = -ENXIO;
struct backlight_device *bd = to_backlight_device(dev);
mutex_lock(&bd->ops_lock);
- if (bd->ops && bd->ops->get_brightness)
- rc = sprintf(buf, "%d\n", bd->ops->get_brightness(bd));
+ if (bd->ops && bd->ops->get_brightness) {
+ rc = bd->ops->get_brightness(bd);
+ if (rc >= 0)
+ rc = sprintf(buf, "%d\n", rc);
+ } else {
+ rc = sprintf(buf, "%d\n", bd->props.brightness);
+ }
mutex_unlock(&bd->ops_lock);
return rc;
}
+static DEVICE_ATTR_RO(actual_brightness);
-static struct class *backlight_class;
+static ssize_t scale_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct backlight_device *bd = to_backlight_device(dev);
+
+ if (WARN_ON(bd->props.scale > BACKLIGHT_SCALE_NON_LINEAR))
+ return sprintf(buf, "unknown\n");
+
+ return sprintf(buf, "%s\n", backlight_scale_types[bd->props.scale]);
+}
+static DEVICE_ATTR_RO(scale);
#ifdef CONFIG_PM_SLEEP
static int backlight_suspend(struct device *dev)
@@ -247,49 +312,56 @@ static void bl_device_release(struct device *dev)
kfree(bd);
}
-static struct device_attribute bl_device_attributes[] = {
- __ATTR(bl_power, 0644, backlight_show_power, backlight_store_power),
- __ATTR(brightness, 0644, backlight_show_brightness,
- backlight_store_brightness),
- __ATTR(actual_brightness, 0444, backlight_show_actual_brightness,
- NULL),
- __ATTR(max_brightness, 0444, backlight_show_max_brightness, NULL),
- __ATTR(type, 0444, backlight_show_type, NULL),
- __ATTR_NULL,
+static struct attribute *bl_device_attrs[] = {
+ &dev_attr_bl_power.attr,
+ &dev_attr_brightness.attr,
+ &dev_attr_actual_brightness.attr,
+ &dev_attr_max_brightness.attr,
+ &dev_attr_scale.attr,
+ &dev_attr_type.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(bl_device);
+
+static const struct class backlight_class = {
+ .name = "backlight",
+ .dev_groups = bl_device_groups,
+ .pm = &backlight_class_dev_pm_ops,
};
/**
* backlight_force_update - tell the backlight subsystem that hardware state
* has changed
* @bd: the backlight device to update
+ * @reason: reason for update
*
* Updates the internal state of the backlight in response to a hardware event,
- * and generate a uevent to notify userspace
+ * and generates an uevent to notify userspace. A backlight driver shall call
+ * backlight_force_update() when the backlight is changed using, for example,
+ * a hot-key. The updated brightness is read using get_brightness() and the
+ * brightness value is reported using an uevent.
*/
void backlight_force_update(struct backlight_device *bd,
enum backlight_update_reason reason)
{
+ int brightness;
+
mutex_lock(&bd->ops_lock);
- if (bd->ops && bd->ops->get_brightness)
- bd->props.brightness = bd->ops->get_brightness(bd);
+ if (bd->ops && bd->ops->get_brightness) {
+ brightness = bd->ops->get_brightness(bd);
+ if (brightness >= 0)
+ bd->props.brightness = brightness;
+ else
+ dev_err(&bd->dev,
+ "Could not update brightness from device: %pe\n",
+ ERR_PTR(brightness));
+ }
mutex_unlock(&bd->ops_lock);
backlight_generate_event(bd, reason);
}
EXPORT_SYMBOL(backlight_force_update);
-/**
- * backlight_device_register - create and register a new object of
- * backlight_device class.
- * @name: the name of the new object(must be the same as the name of the
- * respective framebuffer device).
- * @parent: a pointer to the parent device
- * @devdata: an optional pointer to be stored for private driver use. The
- * methods may retrieve it by using bl_get_data(bd).
- * @ops: the backlight operations structure.
- *
- * Creates and registers new backlight device. Returns either an
- * ERR_PTR() or a pointer to the newly allocated device.
- */
+/* deprecated - use devm_backlight_device_register() */
struct backlight_device *backlight_device_register(const char *name,
struct device *parent, void *devdata, const struct backlight_ops *ops,
const struct backlight_properties *props)
@@ -306,7 +378,7 @@ struct backlight_device *backlight_device_register(const char *name,
mutex_init(&new_bd->update_lock);
mutex_init(&new_bd->ops_lock);
- new_bd->dev.class = backlight_class;
+ new_bd->dev.class = &backlight_class;
new_bd->dev.parent = parent;
new_bd->dev.release = bl_device_release;
dev_set_name(&new_bd->dev, "%s", name);
@@ -326,13 +398,7 @@ struct backlight_device *backlight_device_register(const char *name,
rc = device_register(&new_bd->dev);
if (rc) {
- kfree(new_bd);
- return ERR_PTR(rc);
- }
-
- rc = backlight_register_fb(new_bd);
- if (rc) {
- device_unregister(&new_bd->dev);
+ put_device(&new_bd->dev);
return ERR_PTR(rc);
}
@@ -345,32 +411,83 @@ struct backlight_device *backlight_device_register(const char *name,
mutex_unlock(&pmac_backlight_mutex);
#endif
+ mutex_lock(&backlight_dev_list_mutex);
+ list_add(&new_bd->entry, &backlight_dev_list);
+ mutex_unlock(&backlight_dev_list_mutex);
+
return new_bd;
}
EXPORT_SYMBOL(backlight_device_register);
+/** backlight_device_get_by_type - find first backlight device of a type
+ * @type: the type of backlight device
+ *
+ * Look up the first backlight device of the specified type
+ *
+ * RETURNS:
+ *
+ * Pointer to backlight device if any was found. Otherwise NULL.
+ */
+struct backlight_device *backlight_device_get_by_type(enum backlight_type type)
+{
+ bool found = false;
+ struct backlight_device *bd;
+
+ mutex_lock(&backlight_dev_list_mutex);
+ list_for_each_entry(bd, &backlight_dev_list, entry) {
+ if (bd->props.type == type) {
+ found = true;
+ break;
+ }
+ }
+ mutex_unlock(&backlight_dev_list_mutex);
+
+ return found ? bd : NULL;
+}
+EXPORT_SYMBOL(backlight_device_get_by_type);
+
/**
- * backlight_device_unregister - unregisters a backlight device object.
- * @bd: the backlight device object to be unregistered and freed.
+ * backlight_device_get_by_name - Get backlight device by name
+ * @name: Device name
+ *
+ * This function looks up a backlight device by its name. It obtains a reference
+ * on the backlight device and it is the caller's responsibility to drop the
+ * reference by calling put_device().
*
- * Unregisters a previously registered via backlight_device_register object.
+ * Returns:
+ * A pointer to the backlight device if found, otherwise NULL.
*/
+struct backlight_device *backlight_device_get_by_name(const char *name)
+{
+ struct device *dev;
+
+ dev = class_find_device_by_name(&backlight_class, name);
+
+ return dev ? to_backlight_device(dev) : NULL;
+}
+EXPORT_SYMBOL(backlight_device_get_by_name);
+
+/* deprecated - use devm_backlight_device_unregister() */
void backlight_device_unregister(struct backlight_device *bd)
{
if (!bd)
return;
+ mutex_lock(&backlight_dev_list_mutex);
+ list_del(&bd->entry);
+ mutex_unlock(&backlight_dev_list_mutex);
+
#ifdef CONFIG_PMAC_BACKLIGHT
mutex_lock(&pmac_backlight_mutex);
if (pmac_backlight == bd)
pmac_backlight = NULL;
mutex_unlock(&pmac_backlight_mutex);
#endif
+
mutex_lock(&bd->ops_lock);
bd->ops = NULL;
mutex_unlock(&bd->ops_lock);
- backlight_unregister_fb(bd);
device_unregister(&bd->dev);
}
EXPORT_SYMBOL(backlight_device_unregister);
@@ -391,19 +508,21 @@ static int devm_backlight_device_match(struct device *dev, void *res,
}
/**
- * devm_backlight_device_register - resource managed backlight_device_register()
+ * devm_backlight_device_register - register a new backlight device
* @dev: the device to register
* @name: the name of the device
- * @parent: a pointer to the parent device
+ * @parent: a pointer to the parent device (often the same as @dev)
* @devdata: an optional pointer to be stored for private driver use
* @ops: the backlight operations structure
* @props: the backlight properties
*
- * @return a struct backlight on success, or an ERR_PTR on error
+ * Creates and registers new backlight device. When a backlight device
+ * is registered the configuration must be specified in the @props
+ * parameter. See description of &backlight_properties.
*
- * Managed backlight_device_register(). The backlight_device returned
- * from this function are automatically freed on driver detach.
- * See backlight_device_register() for more information.
+ * RETURNS:
+ *
+ * struct backlight on success, or an ERR_PTR on error
*/
struct backlight_device *devm_backlight_device_register(struct device *dev,
const char *name, struct device *parent, void *devdata,
@@ -431,13 +550,13 @@ struct backlight_device *devm_backlight_device_register(struct device *dev,
EXPORT_SYMBOL(devm_backlight_device_register);
/**
- * devm_backlight_device_unregister - resource managed backlight_device_unregister()
+ * devm_backlight_device_unregister - unregister backlight device
* @dev: the device to unregister
* @bd: the backlight device to unregister
*
- * Deallocated a backlight allocated with devm_backlight_device_register().
+ * Deallocates a backlight allocated with devm_backlight_device_register().
* Normally this function will not need to be called and the resource management
- * code will ensure that the resource is freed.
+ * code will ensure that the resources are freed.
*/
void devm_backlight_device_unregister(struct device *dev,
struct backlight_device *bd)
@@ -472,29 +591,90 @@ struct backlight_device *of_find_backlight_by_node(struct device_node *node)
{
struct device *dev;
- dev = class_find_device(backlight_class, NULL, node, of_parent_match);
+ dev = class_find_device(&backlight_class, NULL, node, of_parent_match);
return dev ? to_backlight_device(dev) : NULL;
}
EXPORT_SYMBOL(of_find_backlight_by_node);
#endif
+static struct backlight_device *of_find_backlight(struct device *dev)
+{
+ struct backlight_device *bd = NULL;
+ struct device_node *np;
+
+ if (!dev)
+ return NULL;
+
+ if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
+ np = of_parse_phandle(dev->of_node, "backlight", 0);
+ if (np) {
+ bd = of_find_backlight_by_node(np);
+ of_node_put(np);
+ if (!bd)
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+ }
+
+ return bd;
+}
+
+static void devm_backlight_release(void *data)
+{
+ struct backlight_device *bd = data;
+
+ put_device(&bd->dev);
+}
+
+/**
+ * devm_of_find_backlight - find backlight for a device
+ * @dev: the device
+ *
+ * This function looks for a property named 'backlight' on the DT node
+ * connected to @dev and looks up the backlight device. The lookup is
+ * device managed so the reference to the backlight device is automatically
+ * dropped on driver detach.
+ *
+ * RETURNS:
+ *
+ * A pointer to the backlight device if found.
+ * Error pointer -EPROBE_DEFER if the DT property is set, but no backlight
+ * device is found. NULL if there's no backlight property.
+ */
+struct backlight_device *devm_of_find_backlight(struct device *dev)
+{
+ struct backlight_device *bd;
+ int ret;
+
+ bd = of_find_backlight(dev);
+ if (IS_ERR_OR_NULL(bd))
+ return bd;
+ ret = devm_add_action_or_reset(dev, devm_backlight_release, bd);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return bd;
+}
+EXPORT_SYMBOL(devm_of_find_backlight);
+
static void __exit backlight_class_exit(void)
{
- class_destroy(backlight_class);
+ class_unregister(&backlight_class);
}
static int __init backlight_class_init(void)
{
- backlight_class = class_create(THIS_MODULE, "backlight");
- if (IS_ERR(backlight_class)) {
- pr_warn("Unable to create backlight class; errno = %ld\n",
- PTR_ERR(backlight_class));
- return PTR_ERR(backlight_class);
+ int ret;
+
+ ret = class_register(&backlight_class);
+ if (ret) {
+ pr_warn("Unable to create backlight class; errno = %d\n", ret);
+ return ret;
}
- backlight_class->dev_attrs = bl_device_attributes;
- backlight_class->pm = &backlight_class_dev_pm_ops;
+ INIT_LIST_HEAD(&backlight_dev_list);
+ mutex_init(&backlight_dev_list_mutex);
+
return 0;
}