6ca017658b
Backlight class attributes are currently easy to implement incorrectly. Moving certain handling into the backlight core prevents this whilst at the same time makes the drivers simpler and consistent. The following changes are included: The brightness attribute only sets and reads the brightness variable in the backlight_properties structure. The power attribute only sets and reads the power variable in the backlight_properties structure. Any framebuffer blanking events change a variable fb_blank in the backlight_properties structure. The backlight driver has only two functions to implement. One function is called when any of the above properties change (to update the backlight brightness), the second is called to return the current backlight brightness value. A new attribute "actual_brightness" is added to return this brightness as determined by the driver having combined all the above factors (and any driver/device specific factors). Additionally, the backlight core takes care of checking the maximum brightness is not exceeded and of turning off the backlight before device removal. The corgi backlight driver is updated to reflect these changes. Signed-off-by: Richard Purdie <rpurdie@rpsys.net> Signed-off-by: Antonino Daplas <adaplas@pol.net> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
291 lines
7.5 KiB
C
291 lines
7.5 KiB
C
/*
|
|
* Backlight Lowlevel Control Abstraction
|
|
*
|
|
* Copyright (C) 2003,2004 Hewlett-Packard Company
|
|
*
|
|
*/
|
|
|
|
#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/fb.h>
|
|
|
|
static ssize_t backlight_show_power(struct class_device *cdev, char *buf)
|
|
{
|
|
int rc = -ENXIO;
|
|
struct backlight_device *bd = to_backlight_device(cdev);
|
|
|
|
down(&bd->sem);
|
|
if (likely(bd->props))
|
|
rc = sprintf(buf, "%d\n", bd->props->power);
|
|
up(&bd->sem);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t backlight_store_power(struct class_device *cdev, const char *buf, size_t count)
|
|
{
|
|
int rc = -ENXIO, power;
|
|
char *endp;
|
|
struct backlight_device *bd = to_backlight_device(cdev);
|
|
|
|
power = simple_strtoul(buf, &endp, 0);
|
|
if (*endp && !isspace(*endp))
|
|
return -EINVAL;
|
|
|
|
down(&bd->sem);
|
|
if (likely(bd->props)) {
|
|
pr_debug("backlight: set power to %d\n", power);
|
|
bd->props->power = power;
|
|
if (likely(bd->props->update_status))
|
|
bd->props->update_status(bd);
|
|
rc = count;
|
|
}
|
|
up(&bd->sem);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t backlight_show_brightness(struct class_device *cdev, char *buf)
|
|
{
|
|
int rc = -ENXIO;
|
|
struct backlight_device *bd = to_backlight_device(cdev);
|
|
|
|
down(&bd->sem);
|
|
if (likely(bd->props))
|
|
rc = sprintf(buf, "%d\n", bd->props->brightness);
|
|
up(&bd->sem);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t backlight_store_brightness(struct class_device *cdev, const char *buf, size_t count)
|
|
{
|
|
int rc = -ENXIO, brightness;
|
|
char *endp;
|
|
struct backlight_device *bd = to_backlight_device(cdev);
|
|
|
|
brightness = simple_strtoul(buf, &endp, 0);
|
|
if (*endp && !isspace(*endp))
|
|
return -EINVAL;
|
|
|
|
down(&bd->sem);
|
|
if (likely(bd->props)) {
|
|
if (brightness > bd->props->max_brightness)
|
|
rc = -EINVAL;
|
|
else {
|
|
pr_debug("backlight: set brightness to %d\n",
|
|
brightness);
|
|
bd->props->brightness = brightness;
|
|
if (likely(bd->props->update_status))
|
|
bd->props->update_status(bd);
|
|
rc = count;
|
|
}
|
|
}
|
|
up(&bd->sem);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t backlight_show_max_brightness(struct class_device *cdev, char *buf)
|
|
{
|
|
int rc = -ENXIO;
|
|
struct backlight_device *bd = to_backlight_device(cdev);
|
|
|
|
down(&bd->sem);
|
|
if (likely(bd->props))
|
|
rc = sprintf(buf, "%d\n", bd->props->max_brightness);
|
|
up(&bd->sem);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t backlight_show_actual_brightness(struct class_device *cdev,
|
|
char *buf)
|
|
{
|
|
int rc = -ENXIO;
|
|
struct backlight_device *bd = to_backlight_device(cdev);
|
|
|
|
down(&bd->sem);
|
|
if (likely(bd->props && bd->props->get_brightness))
|
|
rc = sprintf(buf, "%d\n", bd->props->get_brightness(bd));
|
|
up(&bd->sem);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void backlight_class_release(struct class_device *dev)
|
|
{
|
|
struct backlight_device *bd = to_backlight_device(dev);
|
|
kfree(bd);
|
|
}
|
|
|
|
static struct class backlight_class = {
|
|
.name = "backlight",
|
|
.release = backlight_class_release,
|
|
};
|
|
|
|
#define DECLARE_ATTR(_name,_mode,_show,_store) \
|
|
{ \
|
|
.attr = { .name = __stringify(_name), .mode = _mode, .owner = THIS_MODULE }, \
|
|
.show = _show, \
|
|
.store = _store, \
|
|
}
|
|
|
|
static struct class_device_attribute bl_class_device_attributes[] = {
|
|
DECLARE_ATTR(power, 0644, backlight_show_power, backlight_store_power),
|
|
DECLARE_ATTR(brightness, 0644, backlight_show_brightness,
|
|
backlight_store_brightness),
|
|
DECLARE_ATTR(actual_brightness, 0444, backlight_show_actual_brightness,
|
|
NULL),
|
|
DECLARE_ATTR(max_brightness, 0444, backlight_show_max_brightness, NULL),
|
|
};
|
|
|
|
/* 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)
|
|
{
|
|
struct backlight_device *bd;
|
|
struct fb_event *evdata =(struct fb_event *)data;
|
|
|
|
/* If we aren't interested in this event, skip it immediately ... */
|
|
if (event != FB_EVENT_BLANK)
|
|
return 0;
|
|
|
|
bd = container_of(self, struct backlight_device, fb_notif);
|
|
down(&bd->sem);
|
|
if (bd->props)
|
|
if (!bd->props->check_fb ||
|
|
bd->props->check_fb(evdata->info)) {
|
|
bd->props->fb_blank = *(int *)evdata->data;
|
|
if (likely(bd->props && bd->props->update_status))
|
|
bd->props->update_status(bd);
|
|
}
|
|
up(&bd->sem);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* 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).
|
|
* @devdata: an optional pointer to be stored in the class_device. The
|
|
* methods may retrieve it by using class_get_devdata(&bd->class_dev).
|
|
* @bp: the backlight properties structure.
|
|
*
|
|
* Creates and registers new backlight class_device. Returns either an
|
|
* ERR_PTR() or a pointer to the newly allocated device.
|
|
*/
|
|
struct backlight_device *backlight_device_register(const char *name, void *devdata,
|
|
struct backlight_properties *bp)
|
|
{
|
|
int i, rc;
|
|
struct backlight_device *new_bd;
|
|
|
|
pr_debug("backlight_device_alloc: name=%s\n", name);
|
|
|
|
new_bd = kmalloc(sizeof(struct backlight_device), GFP_KERNEL);
|
|
if (unlikely(!new_bd))
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
init_MUTEX(&new_bd->sem);
|
|
new_bd->props = bp;
|
|
memset(&new_bd->class_dev, 0, sizeof(new_bd->class_dev));
|
|
new_bd->class_dev.class = &backlight_class;
|
|
strlcpy(new_bd->class_dev.class_id, name, KOBJ_NAME_LEN);
|
|
class_set_devdata(&new_bd->class_dev, devdata);
|
|
|
|
rc = class_device_register(&new_bd->class_dev);
|
|
if (unlikely(rc)) {
|
|
error: kfree(new_bd);
|
|
return ERR_PTR(rc);
|
|
}
|
|
|
|
memset(&new_bd->fb_notif, 0, sizeof(new_bd->fb_notif));
|
|
new_bd->fb_notif.notifier_call = fb_notifier_callback;
|
|
|
|
rc = fb_register_client(&new_bd->fb_notif);
|
|
if (unlikely(rc))
|
|
goto error;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bl_class_device_attributes); i++) {
|
|
rc = class_device_create_file(&new_bd->class_dev,
|
|
&bl_class_device_attributes[i]);
|
|
if (unlikely(rc)) {
|
|
while (--i >= 0)
|
|
class_device_remove_file(&new_bd->class_dev,
|
|
&bl_class_device_attributes[i]);
|
|
class_device_unregister(&new_bd->class_dev);
|
|
/* No need to kfree(new_bd) since release() method was called */
|
|
return ERR_PTR(rc);
|
|
}
|
|
}
|
|
|
|
return new_bd;
|
|
}
|
|
EXPORT_SYMBOL(backlight_device_register);
|
|
|
|
/**
|
|
* backlight_device_unregister - unregisters a backlight device object.
|
|
* @bd: the backlight device object to be unregistered and freed.
|
|
*
|
|
* Unregisters a previously registered via backlight_device_register object.
|
|
*/
|
|
void backlight_device_unregister(struct backlight_device *bd)
|
|
{
|
|
int i;
|
|
|
|
if (!bd)
|
|
return;
|
|
|
|
pr_debug("backlight_device_unregister: name=%s\n", bd->class_dev.class_id);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bl_class_device_attributes); i++)
|
|
class_device_remove_file(&bd->class_dev,
|
|
&bl_class_device_attributes[i]);
|
|
|
|
down(&bd->sem);
|
|
if (likely(bd->props && bd->props->update_status)) {
|
|
bd->props->brightness = 0;
|
|
bd->props->power = 0;
|
|
bd->props->update_status(bd);
|
|
}
|
|
|
|
bd->props = NULL;
|
|
up(&bd->sem);
|
|
|
|
fb_unregister_client(&bd->fb_notif);
|
|
|
|
class_device_unregister(&bd->class_dev);
|
|
}
|
|
EXPORT_SYMBOL(backlight_device_unregister);
|
|
|
|
static void __exit backlight_class_exit(void)
|
|
{
|
|
class_unregister(&backlight_class);
|
|
}
|
|
|
|
static int __init backlight_class_init(void)
|
|
{
|
|
return class_register(&backlight_class);
|
|
}
|
|
|
|
/*
|
|
* if this is compiled into the kernel, we need to ensure that the
|
|
* class is registered before users of the class try to register lcd's
|
|
*/
|
|
postcore_initcall(backlight_class_init);
|
|
module_exit(backlight_class_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Jamey Hicks <jamey.hicks@hp.com>, Andrew Zabolotny <zap@homelink.ru>");
|
|
MODULE_DESCRIPTION("Backlight Lowlevel Control Abstraction");
|