File Index
Files and directories
Sign in
FILE

led_bl.c

File preview with syntax highlighting.

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