Skip to content

Commit e14d879

Browse files
ccpalexlag-linaro
authored andcommitted
leds: lp8864: Add support for Texas Instruments LP8864, LP8864S, LP8866 LED-backlights
Add driver for TI LP8864, LP8864S, LP8866 4/6 channel LED-backlight drivers with I2C interface. Link: https://www.ti.com/lit/gpn/lp8864-q1 Link: https://www.ti.com/lit/gpn/lp8864s-q1 Link: https://www.ti.com/lit/gpn/lp8866-q1 Link: https://www.ti.com/lit/gpn/lp8866s-q1 Signed-off-by: Alexander Sverdlin <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Lee Jones <[email protected]>
1 parent efd435a commit e14d879

File tree

4 files changed

+316
-0
lines changed

4 files changed

+316
-0
lines changed

MAINTAINERS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23240,6 +23240,13 @@ S: Supported
2324023240
F: Documentation/devicetree/bindings/iio/dac/ti,dac7612.yaml
2324123241
F: drivers/iio/dac/ti-dac7612.c
2324223242

23243+
TEXAS INSTRUMENTS' LB8864 LED BACKLIGHT DRIVER
23244+
M: Alexander Sverdlin <[email protected]>
23245+
23246+
S: Maintained
23247+
F: Documentation/devicetree/bindings/leds/backlight/ti,lp8864.yaml
23248+
F: drivers/leds/leds-lp8864.c
23249+
2324323250
TEXAS INSTRUMENTS' SYSTEM CONTROL INTERFACE (TISCI) PROTOCOL DRIVER
2324423251
M: Nishanth Menon <[email protected]>
2324523252
M: Tero Kristo <[email protected]>

drivers/leds/Kconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,18 @@ config LEDS_LP8860
513513
on the LP8860 4 channel LED driver using the I2C communication
514514
bus.
515515

516+
config LEDS_LP8864
517+
tristate "LED support for the TI LP8864/LP8866 4/6 channel LED drivers"
518+
depends on LEDS_CLASS && I2C && OF
519+
select REGMAP_I2C
520+
help
521+
If you say yes here you get support for the TI LP8864-Q1,
522+
LP8864S-Q1, LP8866-Q1, LP8866S-Q1 4/6 channel LED backlight
523+
drivers with I2C interface.
524+
525+
To compile this driver as a module, choose M here: the
526+
module will be called leds-lp8864.
527+
516528
config LEDS_CLEVO_MAIL
517529
tristate "Mail LED on Clevo notebook"
518530
depends on LEDS_CLASS && BROKEN

drivers/leds/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ obj-$(CONFIG_LEDS_LP55XX_COMMON) += leds-lp55xx-common.o
5757
obj-$(CONFIG_LEDS_LP8501) += leds-lp8501.o
5858
obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o
5959
obj-$(CONFIG_LEDS_LP8860) += leds-lp8860.o
60+
obj-$(CONFIG_LEDS_LP8864) += leds-lp8864.o
6061
obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o
6162
obj-$(CONFIG_LEDS_MAX5970) += leds-max5970.o
6263
obj-$(CONFIG_LEDS_MAX77650) += leds-max77650.o

drivers/leds/leds-lp8864.c

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* TI LP8864/LP8866 4/6 Channel LED Driver
4+
*
5+
* Copyright (C) 2024 Siemens AG
6+
*
7+
* Based on LP8860 driver by Dan Murphy <[email protected]>
8+
*/
9+
10+
#include <linux/gpio/consumer.h>
11+
#include <linux/i2c.h>
12+
#include <linux/init.h>
13+
#include <linux/leds.h>
14+
#include <linux/module.h>
15+
#include <linux/mutex.h>
16+
#include <linux/of.h>
17+
#include <linux/regmap.h>
18+
#include <linux/regulator/consumer.h>
19+
#include <linux/slab.h>
20+
21+
#define LP8864_BRT_CONTROL 0x00
22+
#define LP8864_USER_CONFIG1 0x04
23+
#define LP8864_BRT_MODE_MASK GENMASK(9, 8)
24+
#define LP8864_BRT_MODE_REG BIT(9) /* Brightness control by DISPLAY_BRT reg */
25+
#define LP8864_SUPPLY_STATUS 0x0e
26+
#define LP8864_BOOST_STATUS 0x10
27+
#define LP8864_LED_STATUS 0x12
28+
#define LP8864_LED_STATUS_WR_MASK GENMASK(14, 9) /* Writeable bits in the LED_STATUS reg */
29+
30+
/* Textual meaning for status bits, starting from bit 1 */
31+
static const char *const lp8864_supply_status_msg[] = {
32+
"Vin under-voltage fault",
33+
"Vin over-voltage fault",
34+
"Vdd under-voltage fault",
35+
"Vin over-current fault",
36+
"Missing charge pump fault",
37+
"Charge pump fault",
38+
"Missing boost sync fault",
39+
"CRC error fault ",
40+
};
41+
42+
/* Textual meaning for status bits, starting from bit 1 */
43+
static const char *const lp8864_boost_status_msg[] = {
44+
"Boost OVP low fault",
45+
"Boost OVP high fault",
46+
"Boost over-current fault",
47+
"Missing boost FSET resistor fault",
48+
"Missing MODE SEL resistor fault",
49+
"Missing LED resistor fault",
50+
"ISET resistor short to ground fault",
51+
"Thermal shutdown fault",
52+
};
53+
54+
/* Textual meaning for every register bit */
55+
static const char *const lp8864_led_status_msg[] = {
56+
"LED 1 fault",
57+
"LED 2 fault",
58+
"LED 3 fault",
59+
"LED 4 fault",
60+
"LED 5 fault",
61+
"LED 6 fault",
62+
"LED open fault",
63+
"LED internal short fault",
64+
"LED short to GND fault",
65+
NULL, NULL, NULL,
66+
"Invalid string configuration fault",
67+
NULL,
68+
"I2C time out fault",
69+
};
70+
71+
/**
72+
* struct lp8864_led
73+
* @client: Pointer to the I2C client
74+
* @led_dev: led class device pointer
75+
* @regmap: Devices register map
76+
* @led_status_mask: Helps to report LED fault only once
77+
*/
78+
struct lp8864_led {
79+
struct i2c_client *client;
80+
struct led_classdev led_dev;
81+
struct regmap *regmap;
82+
u16 led_status_mask;
83+
};
84+
85+
static int lp8864_fault_check(struct lp8864_led *led)
86+
{
87+
int ret, i;
88+
unsigned int val;
89+
90+
ret = regmap_read(led->regmap, LP8864_SUPPLY_STATUS, &val);
91+
if (ret)
92+
goto err;
93+
94+
/* Odd bits are status bits, even bits are clear bits */
95+
for (i = 0; i < ARRAY_SIZE(lp8864_supply_status_msg); i++)
96+
if (val & BIT(i * 2 + 1))
97+
dev_warn(&led->client->dev, "%s\n", lp8864_supply_status_msg[i]);
98+
99+
/*
100+
* Clear bits have an index preceding the corresponding Status bits;
101+
* both have to be written "1" simultaneously to clear the corresponding
102+
* Status bit.
103+
*/
104+
if (val)
105+
ret = regmap_write(led->regmap, LP8864_SUPPLY_STATUS, val >> 1 | val);
106+
if (ret)
107+
goto err;
108+
109+
ret = regmap_read(led->regmap, LP8864_BOOST_STATUS, &val);
110+
if (ret)
111+
goto err;
112+
113+
/* Odd bits are status bits, even bits are clear bits */
114+
for (i = 0; i < ARRAY_SIZE(lp8864_boost_status_msg); i++)
115+
if (val & BIT(i * 2 + 1))
116+
dev_warn(&led->client->dev, "%s\n", lp8864_boost_status_msg[i]);
117+
118+
if (val)
119+
ret = regmap_write(led->regmap, LP8864_BOOST_STATUS, val >> 1 | val);
120+
if (ret)
121+
goto err;
122+
123+
ret = regmap_read(led->regmap, LP8864_LED_STATUS, &val);
124+
if (ret)
125+
goto err;
126+
127+
/*
128+
* Clear already reported faults that maintain their value until device
129+
* power-down
130+
*/
131+
val &= ~led->led_status_mask;
132+
133+
for (i = 0; i < ARRAY_SIZE(lp8864_led_status_msg); i++)
134+
if (lp8864_led_status_msg[i] && val & BIT(i))
135+
dev_warn(&led->client->dev, "%s\n", lp8864_led_status_msg[i]);
136+
137+
/*
138+
* Mark those which maintain their value until device power-down as
139+
* "already reported"
140+
*/
141+
led->led_status_mask |= val & ~LP8864_LED_STATUS_WR_MASK;
142+
143+
/*
144+
* Only bits 14, 12, 10 have to be cleared here, but others are RO,
145+
* we don't care what we write to them.
146+
*/
147+
if (val & LP8864_LED_STATUS_WR_MASK)
148+
ret = regmap_write(led->regmap, LP8864_LED_STATUS, val >> 1 | val);
149+
if (ret)
150+
goto err;
151+
152+
return 0;
153+
154+
err:
155+
dev_err(&led->client->dev, "Failed to read/clear faults (%pe)\n", ERR_PTR(ret));
156+
157+
return ret;
158+
}
159+
160+
static int lp8864_brightness_set(struct led_classdev *led_cdev,
161+
enum led_brightness brt_val)
162+
{
163+
struct lp8864_led *led = container_of(led_cdev, struct lp8864_led, led_dev);
164+
/* Scale 0..LED_FULL into 16-bit HW brightness */
165+
unsigned int val = brt_val * 0xffff / LED_FULL;
166+
int ret;
167+
168+
ret = lp8864_fault_check(led);
169+
if (ret)
170+
return ret;
171+
172+
ret = regmap_write(led->regmap, LP8864_BRT_CONTROL, val);
173+
if (ret)
174+
dev_err(&led->client->dev, "Failed to write brightness value\n");
175+
176+
return ret;
177+
}
178+
179+
static enum led_brightness lp8864_brightness_get(struct led_classdev *led_cdev)
180+
{
181+
struct lp8864_led *led = container_of(led_cdev, struct lp8864_led, led_dev);
182+
unsigned int val;
183+
int ret;
184+
185+
ret = regmap_read(led->regmap, LP8864_BRT_CONTROL, &val);
186+
if (ret) {
187+
dev_err(&led->client->dev, "Failed to read brightness value\n");
188+
return ret;
189+
}
190+
191+
/* Scale 16-bit HW brightness into 0..LED_FULL */
192+
return val * LED_FULL / 0xffff;
193+
}
194+
195+
static const struct regmap_config lp8864_regmap_config = {
196+
.reg_bits = 8,
197+
.val_bits = 16,
198+
.val_format_endian = REGMAP_ENDIAN_LITTLE,
199+
};
200+
201+
static void lp8864_disable_gpio(void *data)
202+
{
203+
struct gpio_desc *gpio = data;
204+
205+
gpiod_set_value(gpio, 0);
206+
}
207+
208+
static int lp8864_probe(struct i2c_client *client)
209+
{
210+
int ret;
211+
struct lp8864_led *led;
212+
struct device_node *np = dev_of_node(&client->dev);
213+
struct device_node *child_node;
214+
struct led_init_data init_data = {};
215+
struct gpio_desc *enable_gpio;
216+
217+
led = devm_kzalloc(&client->dev, sizeof(*led), GFP_KERNEL);
218+
if (!led)
219+
return -ENOMEM;
220+
221+
child_node = of_get_next_available_child(np, NULL);
222+
if (!child_node) {
223+
dev_err(&client->dev, "No LED function defined\n");
224+
return -EINVAL;
225+
}
226+
227+
ret = devm_regulator_get_enable_optional(&client->dev, "vled");
228+
if (ret && ret != -ENODEV)
229+
return dev_err_probe(&client->dev, ret, "Failed to enable vled regulator\n");
230+
231+
enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_HIGH);
232+
if (IS_ERR(enable_gpio))
233+
return dev_err_probe(&client->dev, PTR_ERR(enable_gpio),
234+
"Failed to get enable GPIO\n");
235+
236+
ret = devm_add_action_or_reset(&client->dev, lp8864_disable_gpio, enable_gpio);
237+
if (ret)
238+
return ret;
239+
240+
led->client = client;
241+
led->led_dev.brightness_set_blocking = lp8864_brightness_set;
242+
led->led_dev.brightness_get = lp8864_brightness_get;
243+
244+
led->regmap = devm_regmap_init_i2c(client, &lp8864_regmap_config);
245+
if (IS_ERR(led->regmap))
246+
return dev_err_probe(&client->dev, PTR_ERR(led->regmap),
247+
"Failed to allocate regmap\n");
248+
249+
/* Control brightness by DISPLAY_BRT register */
250+
ret = regmap_update_bits(led->regmap, LP8864_USER_CONFIG1, LP8864_BRT_MODE_MASK,
251+
LP8864_BRT_MODE_REG);
252+
if (ret) {
253+
dev_err(&led->client->dev, "Failed to set brightness control mode\n");
254+
return ret;
255+
}
256+
257+
ret = lp8864_fault_check(led);
258+
if (ret)
259+
return ret;
260+
261+
init_data.fwnode = of_fwnode_handle(child_node);
262+
init_data.devicename = "lp8864";
263+
init_data.default_label = ":display_cluster";
264+
265+
ret = devm_led_classdev_register_ext(&client->dev, &led->led_dev, &init_data);
266+
if (ret)
267+
dev_err(&client->dev, "Failed to register LED device (%pe)\n", ERR_PTR(ret));
268+
269+
return ret;
270+
}
271+
272+
static const struct i2c_device_id lp8864_id[] = {
273+
{ "lp8864" },
274+
{}
275+
};
276+
MODULE_DEVICE_TABLE(i2c, lp8864_id);
277+
278+
static const struct of_device_id of_lp8864_leds_match[] = {
279+
{ .compatible = "ti,lp8864" },
280+
{}
281+
};
282+
MODULE_DEVICE_TABLE(of, of_lp8864_leds_match);
283+
284+
static struct i2c_driver lp8864_driver = {
285+
.driver = {
286+
.name = "lp8864",
287+
.of_match_table = of_lp8864_leds_match,
288+
},
289+
.probe = lp8864_probe,
290+
.id_table = lp8864_id,
291+
};
292+
module_i2c_driver(lp8864_driver);
293+
294+
MODULE_DESCRIPTION("Texas Instruments LP8864/LP8866 LED driver");
295+
MODULE_AUTHOR("Alexander Sverdlin <[email protected]>");
296+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)