fef7764f8b
The intensity of the backlight can be varied from a range of max_brightness to zero. Though most, if not all the pwm based backlight devices start flickering at lower brightness value. And also for each device there exists a brightness value below which the backlight appears to be turned off though the value is not equal to zero. If the range of brightness for a device is from zero to max_brightness. A graph is plotted for brightness Vs intensity for the pwm based backlight device has to be a linear graph. intensity | / | / | / |/ --------- 0 max_brightness But pratically on measuring the above we note that the intensity of backlight goes to zero(OFF) when the value in not zero almost nearing to zero(some x%). so the graph looks like intensity | / | / | / | | ------------ 0 x max_brightness In order to overcome this drawback knowing this x% i.e nothing but the low threshold beyond which the backlight is off and will have no effect, the brightness value is being offset by the low threshold value(retaining the linearity of the graph). Now the graph becomes intensity | / | / | / | / ------------- 0 max_brightness With this for each and every digit increment in the brightness from zero there is a change in the intensity of backlight. Devices having this behaviour can set the low threshold brightness(lth_brightness) and pass the same as platform data else can have it as zero. [akpm@linux-foundation.org: coding-style fixes] Signed-off-by: Arun Murthy <arun.murthy@stericsson.com> Acked-by: Linus Walleij <linus.walleij@stericsson.com> Acked-by: Richard Purdie <rpurdie@linux.intel.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
204 lines
4.9 KiB
C
204 lines
4.9 KiB
C
/*
|
|
* linux/drivers/video/backlight/pwm_bl.c
|
|
*
|
|
* simple PWM based backlight control, board code has to setup
|
|
* 1) pin configuration so PWM waveforms can output
|
|
* 2) platform_data being correctly configured
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/fb.h>
|
|
#include <linux/backlight.h>
|
|
#include <linux/err.h>
|
|
#include <linux/pwm.h>
|
|
#include <linux/pwm_backlight.h>
|
|
#include <linux/slab.h>
|
|
|
|
struct pwm_bl_data {
|
|
struct pwm_device *pwm;
|
|
struct device *dev;
|
|
unsigned int period;
|
|
unsigned int lth_brightness;
|
|
int (*notify)(struct device *,
|
|
int brightness);
|
|
};
|
|
|
|
static int pwm_backlight_update_status(struct backlight_device *bl)
|
|
{
|
|
struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev);
|
|
int brightness = bl->props.brightness;
|
|
int max = bl->props.max_brightness;
|
|
|
|
if (bl->props.power != FB_BLANK_UNBLANK)
|
|
brightness = 0;
|
|
|
|
if (bl->props.fb_blank != FB_BLANK_UNBLANK)
|
|
brightness = 0;
|
|
|
|
if (pb->notify)
|
|
brightness = pb->notify(pb->dev, brightness);
|
|
|
|
if (brightness == 0) {
|
|
pwm_config(pb->pwm, 0, pb->period);
|
|
pwm_disable(pb->pwm);
|
|
} else {
|
|
brightness = pb->lth_brightness +
|
|
(brightness * (pb->period - pb->lth_brightness) / max);
|
|
pwm_config(pb->pwm, brightness, pb->period);
|
|
pwm_enable(pb->pwm);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int pwm_backlight_get_brightness(struct backlight_device *bl)
|
|
{
|
|
return bl->props.brightness;
|
|
}
|
|
|
|
static const struct backlight_ops pwm_backlight_ops = {
|
|
.update_status = pwm_backlight_update_status,
|
|
.get_brightness = pwm_backlight_get_brightness,
|
|
};
|
|
|
|
static int pwm_backlight_probe(struct platform_device *pdev)
|
|
{
|
|
struct backlight_properties props;
|
|
struct platform_pwm_backlight_data *data = pdev->dev.platform_data;
|
|
struct backlight_device *bl;
|
|
struct pwm_bl_data *pb;
|
|
int ret;
|
|
|
|
if (!data) {
|
|
dev_err(&pdev->dev, "failed to find platform data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (data->init) {
|
|
ret = data->init(&pdev->dev);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
pb = kzalloc(sizeof(*pb), GFP_KERNEL);
|
|
if (!pb) {
|
|
dev_err(&pdev->dev, "no memory for state\n");
|
|
ret = -ENOMEM;
|
|
goto err_alloc;
|
|
}
|
|
|
|
pb->period = data->pwm_period_ns;
|
|
pb->notify = data->notify;
|
|
pb->lth_brightness = data->lth_brightness *
|
|
(data->pwm_period_ns / data->max_brightness);
|
|
pb->dev = &pdev->dev;
|
|
|
|
pb->pwm = pwm_request(data->pwm_id, "backlight");
|
|
if (IS_ERR(pb->pwm)) {
|
|
dev_err(&pdev->dev, "unable to request PWM for backlight\n");
|
|
ret = PTR_ERR(pb->pwm);
|
|
goto err_pwm;
|
|
} else
|
|
dev_dbg(&pdev->dev, "got pwm for backlight\n");
|
|
|
|
memset(&props, 0, sizeof(struct backlight_properties));
|
|
props.max_brightness = data->max_brightness;
|
|
bl = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, pb,
|
|
&pwm_backlight_ops, &props);
|
|
if (IS_ERR(bl)) {
|
|
dev_err(&pdev->dev, "failed to register backlight\n");
|
|
ret = PTR_ERR(bl);
|
|
goto err_bl;
|
|
}
|
|
|
|
bl->props.brightness = data->dft_brightness;
|
|
backlight_update_status(bl);
|
|
|
|
platform_set_drvdata(pdev, bl);
|
|
return 0;
|
|
|
|
err_bl:
|
|
pwm_free(pb->pwm);
|
|
err_pwm:
|
|
kfree(pb);
|
|
err_alloc:
|
|
if (data->exit)
|
|
data->exit(&pdev->dev);
|
|
return ret;
|
|
}
|
|
|
|
static int pwm_backlight_remove(struct platform_device *pdev)
|
|
{
|
|
struct platform_pwm_backlight_data *data = pdev->dev.platform_data;
|
|
struct backlight_device *bl = platform_get_drvdata(pdev);
|
|
struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev);
|
|
|
|
backlight_device_unregister(bl);
|
|
pwm_config(pb->pwm, 0, pb->period);
|
|
pwm_disable(pb->pwm);
|
|
pwm_free(pb->pwm);
|
|
kfree(pb);
|
|
if (data->exit)
|
|
data->exit(&pdev->dev);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int pwm_backlight_suspend(struct platform_device *pdev,
|
|
pm_message_t state)
|
|
{
|
|
struct backlight_device *bl = platform_get_drvdata(pdev);
|
|
struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev);
|
|
|
|
if (pb->notify)
|
|
pb->notify(pb->dev, 0);
|
|
pwm_config(pb->pwm, 0, pb->period);
|
|
pwm_disable(pb->pwm);
|
|
return 0;
|
|
}
|
|
|
|
static int pwm_backlight_resume(struct platform_device *pdev)
|
|
{
|
|
struct backlight_device *bl = platform_get_drvdata(pdev);
|
|
|
|
backlight_update_status(bl);
|
|
return 0;
|
|
}
|
|
#else
|
|
#define pwm_backlight_suspend NULL
|
|
#define pwm_backlight_resume NULL
|
|
#endif
|
|
|
|
static struct platform_driver pwm_backlight_driver = {
|
|
.driver = {
|
|
.name = "pwm-backlight",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
.probe = pwm_backlight_probe,
|
|
.remove = pwm_backlight_remove,
|
|
.suspend = pwm_backlight_suspend,
|
|
.resume = pwm_backlight_resume,
|
|
};
|
|
|
|
static int __init pwm_backlight_init(void)
|
|
{
|
|
return platform_driver_register(&pwm_backlight_driver);
|
|
}
|
|
module_init(pwm_backlight_init);
|
|
|
|
static void __exit pwm_backlight_exit(void)
|
|
{
|
|
platform_driver_unregister(&pwm_backlight_driver);
|
|
}
|
|
module_exit(pwm_backlight_exit);
|
|
|
|
MODULE_DESCRIPTION("PWM based Backlight Driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("platform:pwm-backlight");
|
|
|