xref: /linux/drivers/leds/leds-lp8864.c (revision e814f3fd16acfb7f9966773953de8f740a1e3202)
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 <dmurphy@ti.com>
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 <alexander.sverdlin@siemens.com>");
296 MODULE_LICENSE("GPL");
297