690 lines · 18.1 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * Backlight Lowlevel Control Abstraction * * Copyright (C) 2003,2004 Hewlett-Packard Company * */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/module.h>#include <linux/init.h>#include <linux/device.h>#include <linux/backlight.h>#include <linux/notifier.h>#include <linux/ctype.h>#include <linux/err.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",}; 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){	guard(mutex)(&bd->ops_lock); 	if (!bd->ops)		return;	if (bd->ops->controls_device && !bd->ops->controls_device(bd, display_dev))		return; 	if (fb_on && (!prev_fb_on || !bd->use_count)) {		if (!bd->use_count++) {			bd->props.state &= ~BL_CORE_FBBLANK;			backlight_update_status(bd);		}	} 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); void backlight_notify_blank_all(struct device *display_dev, bool fb_on, bool prev_fb_on){	struct backlight_device *bd; 	guard(mutex)(&backlight_dev_list_mutex); 	list_for_each_entry(bd, &backlight_dev_list, entry)		backlight_notify_blank(bd, display_dev, fb_on, prev_fb_on);}EXPORT_SYMBOL(backlight_notify_blank_all); static void backlight_generate_event(struct backlight_device *bd,				     enum backlight_update_reason reason){	char *envp[2]; 	switch (reason) {	case BACKLIGHT_UPDATE_SYSFS:		envp[0] = "SOURCE=sysfs";		break;	case BACKLIGHT_UPDATE_HOTKEY:		envp[0] = "SOURCE=hotkey";		break;	default:		envp[0] = "SOURCE=unknown";		break;	}	envp[1] = NULL;	kobject_uevent_env(&bd->dev.kobj, KOBJ_CHANGE, envp);	sysfs_notify(&bd->dev.kobj, NULL, "actual_brightness");} static ssize_t bl_power_show(struct device *dev, struct device_attribute *attr,		char *buf){	struct backlight_device *bd = to_backlight_device(dev); 	return sysfs_emit(buf, "%d\n", bd->props.power);} 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, old_power; 	rc = kstrtoul(buf, 0, &power);	if (rc)		return rc; 	rc = -ENXIO;	mutex_lock(&bd->ops_lock);	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;			rc = backlight_update_status(bd);			if (rc)				bd->props.power = old_power;			else				rc = count;		} else {			rc = count;		}	}	mutex_unlock(&bd->ops_lock); 	return rc;}static DEVICE_ATTR_RW(bl_power); static ssize_t brightness_show(struct device *dev,		struct device_attribute *attr, char *buf){	struct backlight_device *bd = to_backlight_device(dev); 	return sysfs_emit(buf, "%d\n", bd->props.brightness);} int backlight_device_set_brightness(struct backlight_device *bd,				    unsigned long brightness){	int rc = -ENXIO; 	mutex_lock(&bd->ops_lock);	if (bd->ops) {		if (brightness > bd->props.max_brightness)			rc = -EINVAL;		else {			pr_debug("set brightness to %lu\n", brightness);			bd->props.brightness = brightness;			rc = backlight_update_status(bd);		}	}	mutex_unlock(&bd->ops_lock); 	backlight_generate_event(bd, BACKLIGHT_UPDATE_SYSFS); 	return rc;}EXPORT_SYMBOL(backlight_device_set_brightness); 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 sysfs_emit(buf, "%s\n", backlight_types[bd->props.type]);}static DEVICE_ATTR_RO(type); static ssize_t max_brightness_show(struct device *dev,		struct device_attribute *attr, char *buf){	struct backlight_device *bd = to_backlight_device(dev); 	return sysfs_emit(buf, "%d\n", bd->props.max_brightness);}static DEVICE_ATTR_RO(max_brightness); 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 = bd->ops->get_brightness(bd);		if (rc >= 0)			rc = sysfs_emit(buf, "%d\n", rc);	} else {		rc = sysfs_emit(buf, "%d\n", bd->props.brightness);	}	mutex_unlock(&bd->ops_lock); 	return rc;}static DEVICE_ATTR_RO(actual_brightness); 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 sysfs_emit(buf, "unknown\n"); 	return sysfs_emit(buf, "%s\n", backlight_scale_types[bd->props.scale]);}static DEVICE_ATTR_RO(scale); #ifdef CONFIG_PM_SLEEPstatic int backlight_suspend(struct device *dev){	struct backlight_device *bd = to_backlight_device(dev); 	mutex_lock(&bd->ops_lock);	if (bd->ops && bd->ops->options & BL_CORE_SUSPENDRESUME) {		bd->props.state |= BL_CORE_SUSPENDED;		backlight_update_status(bd);	}	mutex_unlock(&bd->ops_lock); 	return 0;} static int backlight_resume(struct device *dev){	struct backlight_device *bd = to_backlight_device(dev); 	mutex_lock(&bd->ops_lock);	if (bd->ops && bd->ops->options & BL_CORE_SUSPENDRESUME) {		bd->props.state &= ~BL_CORE_SUSPENDED;		backlight_update_status(bd);	}	mutex_unlock(&bd->ops_lock); 	return 0;}#endif static SIMPLE_DEV_PM_OPS(backlight_class_dev_pm_ops, backlight_suspend,			 backlight_resume); static void bl_device_release(struct device *dev){	struct backlight_device *bd = to_backlight_device(dev);	kfree(bd);} 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 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) {		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); /* 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){	struct backlight_device *new_bd;	int rc; 	pr_debug("backlight_device_register: name=%s\n", name); 	new_bd = kzalloc_obj(struct backlight_device);	if (!new_bd)		return ERR_PTR(-ENOMEM); 	mutex_init(&new_bd->update_lock);	mutex_init(&new_bd->ops_lock); 	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);	dev_set_drvdata(&new_bd->dev, devdata); 	/* Set default properties */	if (props) {		memcpy(&new_bd->props, props,		       sizeof(struct backlight_properties));		if (props->type <= 0 || props->type >= BACKLIGHT_TYPE_MAX) {			WARN(1, "%s: invalid backlight type", name);			new_bd->props.type = BACKLIGHT_RAW;		}	} else {		new_bd->props.type = BACKLIGHT_RAW;	} 	rc = device_register(&new_bd->dev);	if (rc) {		put_device(&new_bd->dev);		return ERR_PTR(rc);	} 	new_bd->ops = ops; #ifdef CONFIG_PMAC_BACKLIGHT	mutex_lock(&pmac_backlight_mutex);	if (!pmac_backlight)		pmac_backlight = new_bd;	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_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(). * * 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); 	device_unregister(&bd->dev);}EXPORT_SYMBOL(backlight_device_unregister); static void devm_backlight_device_release(struct device *dev, void *res){	struct backlight_device *backlight = *(struct backlight_device **)res; 	backlight_device_unregister(backlight);}

Lines 1–500 of 690.