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
lp8864_fault_check(struct lp8864_led * led)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
lp8864_brightness_set(struct led_classdev * led_cdev,enum led_brightness brt_val)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
lp8864_brightness_get(struct led_classdev * led_cdev)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
lp8864_disable_gpio(void * data)201 static void lp8864_disable_gpio(void *data)
202 {
203 struct gpio_desc *gpio = data;
204
205 gpiod_set_value(gpio, 0);
206 }
207
lp8864_probe(struct i2c_client * client)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");
297