Skip to content

Commit ae232e4

Browse files
tombatmlind
authored andcommitted
backlight: add led-backlight driver
This patch adds a led-backlight driver (led_bl), which is similar to pwm_bl except the driver uses a LED class driver to adjust the brightness in the HW. Multiple LEDs can be used for a single backlight. Signed-off-by: Tomi Valkeinen <[email protected]> Signed-off-by: Jean-Jacques Hiblot <[email protected]> Acked-by: Pavel Machek <[email protected]> Reviewed-by: Daniel Thompson <[email protected]> Acked-by: Lee Jones <[email protected]> Acked-by: Tony Lindgren <[email protected]> Tested-by: Tony Lindgren <[email protected]> Tested-by: Guido Günther <[email protected]> Signed-off-by: Pavel Machek <[email protected]> Signed-off-by: Tony Lindgren <[email protected]>
1 parent bb6d3fb commit ae232e4

File tree

3 files changed

+268
-0
lines changed

3 files changed

+268
-0
lines changed

drivers/video/backlight/Kconfig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,13 @@ config BACKLIGHT_RAVE_SP
456456
help
457457
Support for backlight control on RAVE SP device.
458458

459+
config BACKLIGHT_LED
460+
tristate "Generic LED based Backlight Driver"
461+
depends on LEDS_CLASS && OF
462+
help
463+
If you have a LCD backlight adjustable by LED class driver, say Y
464+
to enable this driver.
465+
459466
endif # BACKLIGHT_CLASS_DEVICE
460467

461468
endmenu

drivers/video/backlight/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,4 @@ obj-$(CONFIG_BACKLIGHT_TPS65217) += tps65217_bl.o
5757
obj-$(CONFIG_BACKLIGHT_WM831X) += wm831x_bl.o
5858
obj-$(CONFIG_BACKLIGHT_ARCXCNN) += arcxcnn_bl.o
5959
obj-$(CONFIG_BACKLIGHT_RAVE_SP) += rave-sp-backlight.o
60+
obj-$(CONFIG_BACKLIGHT_LED) += led_bl.o

drivers/video/backlight/led_bl.c

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Copyright (C) 2015-2019 Texas Instruments Incorporated - http://www.ti.com/
4+
* Author: Tomi Valkeinen <[email protected]>
5+
*
6+
* Based on pwm_bl.c
7+
*/
8+
9+
#include <linux/backlight.h>
10+
#include <linux/leds.h>
11+
#include <linux/module.h>
12+
#include <linux/platform_device.h>
13+
14+
struct led_bl_data {
15+
struct device *dev;
16+
struct backlight_device *bl_dev;
17+
struct led_classdev **leds;
18+
bool enabled;
19+
int nb_leds;
20+
unsigned int *levels;
21+
unsigned int default_brightness;
22+
unsigned int max_brightness;
23+
};
24+
25+
static void led_bl_set_brightness(struct led_bl_data *priv, int level)
26+
{
27+
int i;
28+
int bkl_brightness;
29+
30+
if (priv->levels)
31+
bkl_brightness = priv->levels[level];
32+
else
33+
bkl_brightness = level;
34+
35+
for (i = 0; i < priv->nb_leds; i++)
36+
led_set_brightness(priv->leds[i], bkl_brightness);
37+
38+
priv->enabled = true;
39+
}
40+
41+
static void led_bl_power_off(struct led_bl_data *priv)
42+
{
43+
int i;
44+
45+
if (!priv->enabled)
46+
return;
47+
48+
for (i = 0; i < priv->nb_leds; i++)
49+
led_set_brightness(priv->leds[i], LED_OFF);
50+
51+
priv->enabled = false;
52+
}
53+
54+
static int led_bl_update_status(struct backlight_device *bl)
55+
{
56+
struct led_bl_data *priv = bl_get_data(bl);
57+
int brightness = bl->props.brightness;
58+
59+
if (bl->props.power != FB_BLANK_UNBLANK ||
60+
bl->props.fb_blank != FB_BLANK_UNBLANK ||
61+
bl->props.state & BL_CORE_FBBLANK)
62+
brightness = 0;
63+
64+
if (brightness > 0)
65+
led_bl_set_brightness(priv, brightness);
66+
else
67+
led_bl_power_off(priv);
68+
69+
return 0;
70+
}
71+
72+
static const struct backlight_ops led_bl_ops = {
73+
.update_status = led_bl_update_status,
74+
};
75+
76+
static int led_bl_get_leds(struct device *dev,
77+
struct led_bl_data *priv)
78+
{
79+
int i, nb_leds, ret;
80+
struct device_node *node = dev->of_node;
81+
struct led_classdev **leds;
82+
unsigned int max_brightness;
83+
unsigned int default_brightness;
84+
85+
ret = of_count_phandle_with_args(node, "leds", NULL);
86+
if (ret < 0) {
87+
dev_err(dev, "Unable to get led count\n");
88+
return -EINVAL;
89+
}
90+
91+
nb_leds = ret;
92+
if (nb_leds < 1) {
93+
dev_err(dev, "At least one LED must be specified!\n");
94+
return -EINVAL;
95+
}
96+
97+
leds = devm_kzalloc(dev, sizeof(struct led_classdev *) * nb_leds,
98+
GFP_KERNEL);
99+
if (!leds)
100+
return -ENOMEM;
101+
102+
for (i = 0; i < nb_leds; i++) {
103+
leds[i] = devm_of_led_get(dev, i);
104+
if (IS_ERR(leds[i]))
105+
return PTR_ERR(leds[i]);
106+
}
107+
108+
/* check that the LEDs all have the same brightness range */
109+
max_brightness = leds[0]->max_brightness;
110+
for (i = 1; i < nb_leds; i++) {
111+
if (max_brightness != leds[i]->max_brightness) {
112+
dev_err(dev, "LEDs must have identical ranges\n");
113+
return -EINVAL;
114+
}
115+
}
116+
117+
/* get the default brightness from the first LED from the list */
118+
default_brightness = leds[0]->brightness;
119+
120+
priv->nb_leds = nb_leds;
121+
priv->leds = leds;
122+
priv->max_brightness = max_brightness;
123+
priv->default_brightness = default_brightness;
124+
125+
return 0;
126+
}
127+
128+
static int led_bl_parse_levels(struct device *dev,
129+
struct led_bl_data *priv)
130+
{
131+
struct device_node *node = dev->of_node;
132+
int num_levels;
133+
u32 value;
134+
int ret;
135+
136+
if (!node)
137+
return -ENODEV;
138+
139+
num_levels = of_property_count_u32_elems(node, "brightness-levels");
140+
if (num_levels > 1) {
141+
int i;
142+
unsigned int db;
143+
u32 *levels = NULL;
144+
145+
levels = devm_kzalloc(dev, sizeof(u32) * num_levels,
146+
GFP_KERNEL);
147+
if (!levels)
148+
return -ENOMEM;
149+
150+
ret = of_property_read_u32_array(node, "brightness-levels",
151+
levels,
152+
num_levels);
153+
if (ret < 0)
154+
return ret;
155+
156+
/*
157+
* Try to map actual LED brightness to backlight brightness
158+
* level
159+
*/
160+
db = priv->default_brightness;
161+
for (i = 0 ; i < num_levels; i++) {
162+
if ((i && db > levels[i-1]) && db <= levels[i])
163+
break;
164+
}
165+
priv->default_brightness = i;
166+
priv->max_brightness = num_levels - 1;
167+
priv->levels = levels;
168+
} else if (num_levels >= 0)
169+
dev_warn(dev, "Not enough levels defined\n");
170+
171+
ret = of_property_read_u32(node, "default-brightness-level", &value);
172+
if (!ret && value <= priv->max_brightness)
173+
priv->default_brightness = value;
174+
else if (!ret && value > priv->max_brightness)
175+
dev_warn(dev, "Invalid default brightness. Ignoring it\n");
176+
177+
return 0;
178+
}
179+
180+
static int led_bl_probe(struct platform_device *pdev)
181+
{
182+
struct backlight_properties props;
183+
struct led_bl_data *priv;
184+
int ret, i;
185+
186+
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
187+
if (!priv)
188+
return -ENOMEM;
189+
190+
platform_set_drvdata(pdev, priv);
191+
192+
priv->dev = &pdev->dev;
193+
194+
ret = led_bl_get_leds(&pdev->dev, priv);
195+
if (ret)
196+
return ret;
197+
198+
ret = led_bl_parse_levels(&pdev->dev, priv);
199+
if (ret < 0) {
200+
dev_err(&pdev->dev, "Failed to parse DT data\n");
201+
return ret;
202+
}
203+
204+
memset(&props, 0, sizeof(struct backlight_properties));
205+
props.type = BACKLIGHT_RAW;
206+
props.max_brightness = priv->max_brightness;
207+
props.brightness = priv->default_brightness;
208+
props.power = (priv->default_brightness > 0) ? FB_BLANK_POWERDOWN :
209+
FB_BLANK_UNBLANK;
210+
priv->bl_dev = backlight_device_register(dev_name(&pdev->dev),
211+
&pdev->dev, priv, &led_bl_ops, &props);
212+
if (IS_ERR(priv->bl_dev)) {
213+
dev_err(&pdev->dev, "Failed to register backlight\n");
214+
return PTR_ERR(priv->bl_dev);
215+
}
216+
217+
for (i = 0; i < priv->nb_leds; i++)
218+
led_sysfs_disable(priv->leds[i]);
219+
220+
backlight_update_status(priv->bl_dev);
221+
222+
return 0;
223+
}
224+
225+
static int led_bl_remove(struct platform_device *pdev)
226+
{
227+
struct led_bl_data *priv = platform_get_drvdata(pdev);
228+
struct backlight_device *bl = priv->bl_dev;
229+
int i;
230+
231+
backlight_device_unregister(bl);
232+
233+
led_bl_power_off(priv);
234+
for (i = 0; i < priv->nb_leds; i++)
235+
led_sysfs_enable(priv->leds[i]);
236+
237+
return 0;
238+
}
239+
240+
static const struct of_device_id led_bl_of_match[] = {
241+
{ .compatible = "led-backlight" },
242+
{ }
243+
};
244+
245+
MODULE_DEVICE_TABLE(of, led_bl_of_match);
246+
247+
static struct platform_driver led_bl_driver = {
248+
.driver = {
249+
.name = "led-backlight",
250+
.of_match_table = of_match_ptr(led_bl_of_match),
251+
},
252+
.probe = led_bl_probe,
253+
.remove = led_bl_remove,
254+
};
255+
256+
module_platform_driver(led_bl_driver);
257+
258+
MODULE_DESCRIPTION("LED based Backlight Driver");
259+
MODULE_LICENSE("GPL");
260+
MODULE_ALIAS("platform:led-backlight");

0 commit comments

Comments
 (0)