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
14struct 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
25static 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
41static 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
54static int led_bl_update_status(struct backlight_device *bl)
55{
56	struct led_bl_data *priv = bl_get_data(bl);
57	int brightness = backlight_get_brightness(bl);
58
59	if (brightness > 0)
60		led_bl_set_brightness(priv, brightness);
61	else
62		led_bl_power_off(priv);
63
64	return 0;
65}
66
67static const struct backlight_ops led_bl_ops = {
68	.update_status	= led_bl_update_status,
69};
70
71static 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(node, "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, 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, i);
99		if (IS_ERR(leds[i]))
100			return PTR_ERR(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
123static 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(node, "brightness-levels");
135	if (num_levels > 1) {
136		int i;
137		unsigned int db;
138		u32 *levels = NULL;
139
140		levels = devm_kzalloc(dev, sizeof(u32) * num_levels,
141				      GFP_KERNEL);
142		if (!levels)
143			return -ENOMEM;
144
145		ret = of_property_read_u32_array(node, "brightness-levels",
146						levels,
147						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(node, "default-brightness-level", &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
175static 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(&pdev->dev, sizeof(*priv), GFP_KERNEL);
182	if (!priv)
183		return -ENOMEM;
184
185	platform_set_drvdata(pdev, priv);
186
187	priv->dev = &pdev->dev;
188
189	ret = led_bl_get_leds(&pdev->dev, priv);
190	if (ret)
191		return ret;
192
193	ret = led_bl_parse_levels(&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(dev_name(&pdev->dev),
206			&pdev->dev, priv, &led_bl_ops, &props);
207	if (IS_ERR(priv->bl_dev)) {
208		dev_err(&pdev->dev, "Failed to register backlight\n");
209		return PTR_ERR(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(priv->leds[i]);
215		mutex_unlock(&priv->leds[i]->led_access);
216	}
217
218	backlight_update_status(priv->bl_dev);
219
220	return 0;
221}
222
223static 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(bl);
230
231	led_bl_power_off(priv);
232	for (i = 0; i < priv->nb_leds; i++)
233		led_sysfs_enable(priv->leds[i]);
234}
235
236static const struct of_device_id led_bl_of_match[] = {
237	{ .compatible = "led-backlight" },
238	{ }
239};
240
241MODULE_DEVICE_TABLE(of, led_bl_of_match);
242
243static 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
252module_platform_driver(led_bl_driver);
253
254MODULE_DESCRIPTION("LED based Backlight Driver");
255MODULE_LICENSE("GPL");
256MODULE_ALIAS("platform:led-backlight");
257