1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2015-2019 Texas Instruments Incorporated - http://www.ti.com/ |
4 | * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> |
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(led_cdev: priv->leds[i], brightness: 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(led_cdev: priv->leds[i], brightness: 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_dev: bl); |
57 | int brightness = backlight_get_brightness(bd: bl); |
58 | |
59 | if (brightness > 0) |
60 | led_bl_set_brightness(priv, level: brightness); |
61 | else |
62 | led_bl_power_off(priv); |
63 | |
64 | return 0; |
65 | } |
66 | |
67 | static const struct backlight_ops led_bl_ops = { |
68 | .update_status = led_bl_update_status, |
69 | }; |
70 | |
71 | static int led_bl_get_leds(struct device *dev, |
72 | struct led_bl_data *priv) |
73 | { |
74 | int i, nb_leds, ret; |
75 | struct device_node *node = dev->of_node; |
76 | struct led_classdev **leds; |
77 | unsigned int max_brightness; |
78 | unsigned int default_brightness; |
79 | |
80 | ret = of_count_phandle_with_args(np: node, list_name: "leds" , NULL); |
81 | if (ret < 0) { |
82 | dev_err(dev, "Unable to get led count\n" ); |
83 | return -EINVAL; |
84 | } |
85 | |
86 | nb_leds = ret; |
87 | if (nb_leds < 1) { |
88 | dev_err(dev, "At least one LED must be specified!\n" ); |
89 | return -EINVAL; |
90 | } |
91 | |
92 | leds = devm_kzalloc(dev, size: sizeof(struct led_classdev *) * nb_leds, |
93 | GFP_KERNEL); |
94 | if (!leds) |
95 | return -ENOMEM; |
96 | |
97 | for (i = 0; i < nb_leds; i++) { |
98 | leds[i] = devm_of_led_get(dev, index: i); |
99 | if (IS_ERR(ptr: leds[i])) |
100 | return PTR_ERR(ptr: leds[i]); |
101 | } |
102 | |
103 | /* check that the LEDs all have the same brightness range */ |
104 | max_brightness = leds[0]->max_brightness; |
105 | for (i = 1; i < nb_leds; i++) { |
106 | if (max_brightness != leds[i]->max_brightness) { |
107 | dev_err(dev, "LEDs must have identical ranges\n" ); |
108 | return -EINVAL; |
109 | } |
110 | } |
111 | |
112 | /* get the default brightness from the first LED from the list */ |
113 | default_brightness = leds[0]->brightness; |
114 | |
115 | priv->nb_leds = nb_leds; |
116 | priv->leds = leds; |
117 | priv->max_brightness = max_brightness; |
118 | priv->default_brightness = default_brightness; |
119 | |
120 | return 0; |
121 | } |
122 | |
123 | static int led_bl_parse_levels(struct device *dev, |
124 | struct led_bl_data *priv) |
125 | { |
126 | struct device_node *node = dev->of_node; |
127 | int num_levels; |
128 | u32 value; |
129 | int ret; |
130 | |
131 | if (!node) |
132 | return -ENODEV; |
133 | |
134 | num_levels = of_property_count_u32_elems(np: node, propname: "brightness-levels" ); |
135 | if (num_levels > 1) { |
136 | int i; |
137 | unsigned int db; |
138 | u32 *levels = NULL; |
139 | |
140 | levels = devm_kzalloc(dev, size: sizeof(u32) * num_levels, |
141 | GFP_KERNEL); |
142 | if (!levels) |
143 | return -ENOMEM; |
144 | |
145 | ret = of_property_read_u32_array(np: node, propname: "brightness-levels" , |
146 | out_values: levels, |
147 | sz: num_levels); |
148 | if (ret < 0) |
149 | return ret; |
150 | |
151 | /* |
152 | * Try to map actual LED brightness to backlight brightness |
153 | * level |
154 | */ |
155 | db = priv->default_brightness; |
156 | for (i = 0 ; i < num_levels; i++) { |
157 | if ((i && db > levels[i-1]) && db <= levels[i]) |
158 | break; |
159 | } |
160 | priv->default_brightness = i; |
161 | priv->max_brightness = num_levels - 1; |
162 | priv->levels = levels; |
163 | } else if (num_levels >= 0) |
164 | dev_warn(dev, "Not enough levels defined\n" ); |
165 | |
166 | ret = of_property_read_u32(np: node, propname: "default-brightness-level" , out_value: &value); |
167 | if (!ret && value <= priv->max_brightness) |
168 | priv->default_brightness = value; |
169 | else if (!ret && value > priv->max_brightness) |
170 | dev_warn(dev, "Invalid default brightness. Ignoring it\n" ); |
171 | |
172 | return 0; |
173 | } |
174 | |
175 | static int led_bl_probe(struct platform_device *pdev) |
176 | { |
177 | struct backlight_properties props; |
178 | struct led_bl_data *priv; |
179 | int ret, i; |
180 | |
181 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
182 | if (!priv) |
183 | return -ENOMEM; |
184 | |
185 | platform_set_drvdata(pdev, data: priv); |
186 | |
187 | priv->dev = &pdev->dev; |
188 | |
189 | ret = led_bl_get_leds(dev: &pdev->dev, priv); |
190 | if (ret) |
191 | return ret; |
192 | |
193 | ret = led_bl_parse_levels(dev: &pdev->dev, priv); |
194 | if (ret < 0) { |
195 | dev_err(&pdev->dev, "Failed to parse DT data\n" ); |
196 | return ret; |
197 | } |
198 | |
199 | memset(&props, 0, sizeof(struct backlight_properties)); |
200 | props.type = BACKLIGHT_RAW; |
201 | props.max_brightness = priv->max_brightness; |
202 | props.brightness = priv->default_brightness; |
203 | props.power = (priv->default_brightness > 0) ? FB_BLANK_POWERDOWN : |
204 | FB_BLANK_UNBLANK; |
205 | priv->bl_dev = backlight_device_register(name: dev_name(dev: &pdev->dev), |
206 | dev: &pdev->dev, devdata: priv, ops: &led_bl_ops, props: &props); |
207 | if (IS_ERR(ptr: priv->bl_dev)) { |
208 | dev_err(&pdev->dev, "Failed to register backlight\n" ); |
209 | return PTR_ERR(ptr: priv->bl_dev); |
210 | } |
211 | |
212 | for (i = 0; i < priv->nb_leds; i++) { |
213 | mutex_lock(&priv->leds[i]->led_access); |
214 | led_sysfs_disable(led_cdev: priv->leds[i]); |
215 | mutex_unlock(lock: &priv->leds[i]->led_access); |
216 | } |
217 | |
218 | backlight_update_status(bd: priv->bl_dev); |
219 | |
220 | return 0; |
221 | } |
222 | |
223 | static void led_bl_remove(struct platform_device *pdev) |
224 | { |
225 | struct led_bl_data *priv = platform_get_drvdata(pdev); |
226 | struct backlight_device *bl = priv->bl_dev; |
227 | int i; |
228 | |
229 | backlight_device_unregister(bd: bl); |
230 | |
231 | led_bl_power_off(priv); |
232 | for (i = 0; i < priv->nb_leds; i++) |
233 | led_sysfs_enable(led_cdev: priv->leds[i]); |
234 | } |
235 | |
236 | static const struct of_device_id led_bl_of_match[] = { |
237 | { .compatible = "led-backlight" }, |
238 | { } |
239 | }; |
240 | |
241 | MODULE_DEVICE_TABLE(of, led_bl_of_match); |
242 | |
243 | static struct platform_driver led_bl_driver = { |
244 | .driver = { |
245 | .name = "led-backlight" , |
246 | .of_match_table = led_bl_of_match, |
247 | }, |
248 | .probe = led_bl_probe, |
249 | .remove_new = led_bl_remove, |
250 | }; |
251 | |
252 | module_platform_driver(led_bl_driver); |
253 | |
254 | MODULE_DESCRIPTION("LED based Backlight Driver" ); |
255 | MODULE_LICENSE("GPL" ); |
256 | MODULE_ALIAS("platform:led-backlight" ); |
257 | |