1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * TI LP8860 4-Channel LED Driver |
4 | * |
5 | * Copyright (C) 2014 Texas Instruments |
6 | * |
7 | * Author: Dan Murphy <dmurphy@ti.com> |
8 | */ |
9 | |
10 | #include <linux/i2c.h> |
11 | #include <linux/init.h> |
12 | #include <linux/leds.h> |
13 | #include <linux/regmap.h> |
14 | #include <linux/regulator/consumer.h> |
15 | #include <linux/module.h> |
16 | #include <linux/mutex.h> |
17 | #include <linux/of.h> |
18 | #include <linux/gpio/consumer.h> |
19 | #include <linux/slab.h> |
20 | |
21 | #define LP8860_DISP_CL1_BRT_MSB 0x00 |
22 | #define LP8860_DISP_CL1_BRT_LSB 0x01 |
23 | #define LP8860_DISP_CL1_CURR_MSB 0x02 |
24 | #define LP8860_DISP_CL1_CURR_LSB 0x03 |
25 | #define LP8860_CL2_BRT_MSB 0x04 |
26 | #define LP8860_CL2_BRT_LSB 0x05 |
27 | #define LP8860_CL2_CURRENT 0x06 |
28 | #define LP8860_CL3_BRT_MSB 0x07 |
29 | #define LP8860_CL3_BRT_LSB 0x08 |
30 | #define LP8860_CL3_CURRENT 0x09 |
31 | #define LP8860_CL4_BRT_MSB 0x0a |
32 | #define LP8860_CL4_BRT_LSB 0x0b |
33 | #define LP8860_CL4_CURRENT 0x0c |
34 | #define LP8860_CONFIG 0x0d |
35 | #define LP8860_STATUS 0x0e |
36 | #define LP8860_FAULT 0x0f |
37 | #define LP8860_LED_FAULT 0x10 |
38 | #define LP8860_FAULT_CLEAR 0x11 |
39 | #define LP8860_ID 0x12 |
40 | #define LP8860_TEMP_MSB 0x13 |
41 | #define LP8860_TEMP_LSB 0x14 |
42 | #define LP8860_DISP_LED_CURR_MSB 0x15 |
43 | #define LP8860_DISP_LED_CURR_LSB 0x16 |
44 | #define LP8860_DISP_LED_PWM_MSB 0x17 |
45 | #define LP8860_DISP_LED_PWM_LSB 0x18 |
46 | #define LP8860_EEPROM_CNTRL 0x19 |
47 | #define LP8860_EEPROM_UNLOCK 0x1a |
48 | |
49 | #define LP8860_EEPROM_REG_0 0x60 |
50 | #define LP8860_EEPROM_REG_1 0x61 |
51 | #define LP8860_EEPROM_REG_2 0x62 |
52 | #define LP8860_EEPROM_REG_3 0x63 |
53 | #define LP8860_EEPROM_REG_4 0x64 |
54 | #define LP8860_EEPROM_REG_5 0x65 |
55 | #define LP8860_EEPROM_REG_6 0x66 |
56 | #define LP8860_EEPROM_REG_7 0x67 |
57 | #define LP8860_EEPROM_REG_8 0x68 |
58 | #define LP8860_EEPROM_REG_9 0x69 |
59 | #define LP8860_EEPROM_REG_10 0x6a |
60 | #define LP8860_EEPROM_REG_11 0x6b |
61 | #define LP8860_EEPROM_REG_12 0x6c |
62 | #define LP8860_EEPROM_REG_13 0x6d |
63 | #define LP8860_EEPROM_REG_14 0x6e |
64 | #define LP8860_EEPROM_REG_15 0x6f |
65 | #define LP8860_EEPROM_REG_16 0x70 |
66 | #define LP8860_EEPROM_REG_17 0x71 |
67 | #define LP8860_EEPROM_REG_18 0x72 |
68 | #define LP8860_EEPROM_REG_19 0x73 |
69 | #define LP8860_EEPROM_REG_20 0x74 |
70 | #define LP8860_EEPROM_REG_21 0x75 |
71 | #define LP8860_EEPROM_REG_22 0x76 |
72 | #define LP8860_EEPROM_REG_23 0x77 |
73 | #define LP8860_EEPROM_REG_24 0x78 |
74 | |
75 | #define LP8860_LOCK_EEPROM 0x00 |
76 | #define LP8860_UNLOCK_EEPROM 0x01 |
77 | #define LP8860_PROGRAM_EEPROM 0x02 |
78 | #define LP8860_EEPROM_CODE_1 0x08 |
79 | #define LP8860_EEPROM_CODE_2 0xba |
80 | #define LP8860_EEPROM_CODE_3 0xef |
81 | |
82 | #define LP8860_CLEAR_FAULTS 0x01 |
83 | |
84 | #define LP8860_NAME "lp8860" |
85 | |
86 | /** |
87 | * struct lp8860_led |
88 | * @lock: Lock for reading/writing the device |
89 | * @client: Pointer to the I2C client |
90 | * @led_dev: led class device pointer |
91 | * @regmap: Devices register map |
92 | * @eeprom_regmap: EEPROM register map |
93 | * @enable_gpio: VDDIO/EN gpio to enable communication interface |
94 | * @regulator: LED supply regulator pointer |
95 | */ |
96 | struct lp8860_led { |
97 | struct mutex lock; |
98 | struct i2c_client *client; |
99 | struct led_classdev led_dev; |
100 | struct regmap *regmap; |
101 | struct regmap *eeprom_regmap; |
102 | struct gpio_desc *enable_gpio; |
103 | struct regulator *regulator; |
104 | }; |
105 | |
106 | struct lp8860_eeprom_reg { |
107 | uint8_t reg; |
108 | uint8_t value; |
109 | }; |
110 | |
111 | static struct lp8860_eeprom_reg lp8860_eeprom_disp_regs[] = { |
112 | { LP8860_EEPROM_REG_0, 0xed }, |
113 | { LP8860_EEPROM_REG_1, 0xdf }, |
114 | { LP8860_EEPROM_REG_2, 0xdc }, |
115 | { LP8860_EEPROM_REG_3, 0xf0 }, |
116 | { LP8860_EEPROM_REG_4, 0xdf }, |
117 | { LP8860_EEPROM_REG_5, 0xe5 }, |
118 | { LP8860_EEPROM_REG_6, 0xf2 }, |
119 | { LP8860_EEPROM_REG_7, 0x77 }, |
120 | { LP8860_EEPROM_REG_8, 0x77 }, |
121 | { LP8860_EEPROM_REG_9, 0x71 }, |
122 | { LP8860_EEPROM_REG_10, 0x3f }, |
123 | { LP8860_EEPROM_REG_11, 0xb7 }, |
124 | { LP8860_EEPROM_REG_12, 0x17 }, |
125 | { LP8860_EEPROM_REG_13, 0xef }, |
126 | { LP8860_EEPROM_REG_14, 0xb0 }, |
127 | { LP8860_EEPROM_REG_15, 0x87 }, |
128 | { LP8860_EEPROM_REG_16, 0xce }, |
129 | { LP8860_EEPROM_REG_17, 0x72 }, |
130 | { LP8860_EEPROM_REG_18, 0xe5 }, |
131 | { LP8860_EEPROM_REG_19, 0xdf }, |
132 | { LP8860_EEPROM_REG_20, 0x35 }, |
133 | { LP8860_EEPROM_REG_21, 0x06 }, |
134 | { LP8860_EEPROM_REG_22, 0xdc }, |
135 | { LP8860_EEPROM_REG_23, 0x88 }, |
136 | { LP8860_EEPROM_REG_24, 0x3E }, |
137 | }; |
138 | |
139 | static int lp8860_unlock_eeprom(struct lp8860_led *led, int lock) |
140 | { |
141 | int ret; |
142 | |
143 | mutex_lock(&led->lock); |
144 | |
145 | if (lock == LP8860_UNLOCK_EEPROM) { |
146 | ret = regmap_write(map: led->regmap, |
147 | LP8860_EEPROM_UNLOCK, |
148 | LP8860_EEPROM_CODE_1); |
149 | if (ret) { |
150 | dev_err(&led->client->dev, "EEPROM Unlock failed\n" ); |
151 | goto out; |
152 | } |
153 | |
154 | ret = regmap_write(map: led->regmap, |
155 | LP8860_EEPROM_UNLOCK, |
156 | LP8860_EEPROM_CODE_2); |
157 | if (ret) { |
158 | dev_err(&led->client->dev, "EEPROM Unlock failed\n" ); |
159 | goto out; |
160 | } |
161 | ret = regmap_write(map: led->regmap, |
162 | LP8860_EEPROM_UNLOCK, |
163 | LP8860_EEPROM_CODE_3); |
164 | if (ret) { |
165 | dev_err(&led->client->dev, "EEPROM Unlock failed\n" ); |
166 | goto out; |
167 | } |
168 | } else { |
169 | ret = regmap_write(map: led->regmap, |
170 | LP8860_EEPROM_UNLOCK, |
171 | LP8860_LOCK_EEPROM); |
172 | } |
173 | |
174 | out: |
175 | mutex_unlock(lock: &led->lock); |
176 | return ret; |
177 | } |
178 | |
179 | static int lp8860_fault_check(struct lp8860_led *led) |
180 | { |
181 | int ret, fault; |
182 | unsigned int read_buf; |
183 | |
184 | ret = regmap_read(map: led->regmap, LP8860_LED_FAULT, val: &read_buf); |
185 | if (ret) |
186 | goto out; |
187 | |
188 | fault = read_buf; |
189 | |
190 | ret = regmap_read(map: led->regmap, LP8860_FAULT, val: &read_buf); |
191 | if (ret) |
192 | goto out; |
193 | |
194 | fault |= read_buf; |
195 | |
196 | /* Attempt to clear any faults */ |
197 | if (fault) |
198 | ret = regmap_write(map: led->regmap, LP8860_FAULT_CLEAR, |
199 | LP8860_CLEAR_FAULTS); |
200 | out: |
201 | return ret; |
202 | } |
203 | |
204 | static int lp8860_brightness_set(struct led_classdev *led_cdev, |
205 | enum led_brightness brt_val) |
206 | { |
207 | struct lp8860_led *led = |
208 | container_of(led_cdev, struct lp8860_led, led_dev); |
209 | int disp_brightness = brt_val * 255; |
210 | int ret; |
211 | |
212 | mutex_lock(&led->lock); |
213 | |
214 | ret = lp8860_fault_check(led); |
215 | if (ret) { |
216 | dev_err(&led->client->dev, "Cannot read/clear faults\n" ); |
217 | goto out; |
218 | } |
219 | |
220 | ret = regmap_write(map: led->regmap, LP8860_DISP_CL1_BRT_MSB, |
221 | val: (disp_brightness & 0xff00) >> 8); |
222 | if (ret) { |
223 | dev_err(&led->client->dev, "Cannot write CL1 MSB\n" ); |
224 | goto out; |
225 | } |
226 | |
227 | ret = regmap_write(map: led->regmap, LP8860_DISP_CL1_BRT_LSB, |
228 | val: disp_brightness & 0xff); |
229 | if (ret) { |
230 | dev_err(&led->client->dev, "Cannot write CL1 LSB\n" ); |
231 | goto out; |
232 | } |
233 | out: |
234 | mutex_unlock(lock: &led->lock); |
235 | return ret; |
236 | } |
237 | |
238 | static int lp8860_init(struct lp8860_led *led) |
239 | { |
240 | unsigned int read_buf; |
241 | int ret, i, reg_count; |
242 | |
243 | if (led->regulator) { |
244 | ret = regulator_enable(regulator: led->regulator); |
245 | if (ret) { |
246 | dev_err(&led->client->dev, |
247 | "Failed to enable regulator\n" ); |
248 | return ret; |
249 | } |
250 | } |
251 | |
252 | gpiod_direction_output(desc: led->enable_gpio, value: 1); |
253 | |
254 | ret = lp8860_fault_check(led); |
255 | if (ret) |
256 | goto out; |
257 | |
258 | ret = regmap_read(map: led->regmap, LP8860_STATUS, val: &read_buf); |
259 | if (ret) |
260 | goto out; |
261 | |
262 | ret = lp8860_unlock_eeprom(led, LP8860_UNLOCK_EEPROM); |
263 | if (ret) { |
264 | dev_err(&led->client->dev, "Failed unlocking EEPROM\n" ); |
265 | goto out; |
266 | } |
267 | |
268 | reg_count = ARRAY_SIZE(lp8860_eeprom_disp_regs) / sizeof(lp8860_eeprom_disp_regs[0]); |
269 | for (i = 0; i < reg_count; i++) { |
270 | ret = regmap_write(map: led->eeprom_regmap, |
271 | reg: lp8860_eeprom_disp_regs[i].reg, |
272 | val: lp8860_eeprom_disp_regs[i].value); |
273 | if (ret) { |
274 | dev_err(&led->client->dev, "Failed writing EEPROM\n" ); |
275 | goto out; |
276 | } |
277 | } |
278 | |
279 | ret = lp8860_unlock_eeprom(led, LP8860_LOCK_EEPROM); |
280 | if (ret) |
281 | goto out; |
282 | |
283 | ret = regmap_write(map: led->regmap, |
284 | LP8860_EEPROM_CNTRL, |
285 | LP8860_PROGRAM_EEPROM); |
286 | if (ret) { |
287 | dev_err(&led->client->dev, "Failed programming EEPROM\n" ); |
288 | goto out; |
289 | } |
290 | |
291 | return ret; |
292 | |
293 | out: |
294 | if (ret) |
295 | gpiod_direction_output(desc: led->enable_gpio, value: 0); |
296 | |
297 | if (led->regulator) { |
298 | ret = regulator_disable(regulator: led->regulator); |
299 | if (ret) |
300 | dev_err(&led->client->dev, |
301 | "Failed to disable regulator\n" ); |
302 | } |
303 | |
304 | return ret; |
305 | } |
306 | |
307 | static const struct reg_default lp8860_reg_defs[] = { |
308 | { LP8860_DISP_CL1_BRT_MSB, 0x00}, |
309 | { LP8860_DISP_CL1_BRT_LSB, 0x00}, |
310 | { LP8860_DISP_CL1_CURR_MSB, 0x00}, |
311 | { LP8860_DISP_CL1_CURR_LSB, 0x00}, |
312 | { LP8860_CL2_BRT_MSB, 0x00}, |
313 | { LP8860_CL2_BRT_LSB, 0x00}, |
314 | { LP8860_CL2_CURRENT, 0x00}, |
315 | { LP8860_CL3_BRT_MSB, 0x00}, |
316 | { LP8860_CL3_BRT_LSB, 0x00}, |
317 | { LP8860_CL3_CURRENT, 0x00}, |
318 | { LP8860_CL4_BRT_MSB, 0x00}, |
319 | { LP8860_CL4_BRT_LSB, 0x00}, |
320 | { LP8860_CL4_CURRENT, 0x00}, |
321 | { LP8860_CONFIG, 0x00}, |
322 | { LP8860_FAULT_CLEAR, 0x00}, |
323 | { LP8860_EEPROM_CNTRL, 0x80}, |
324 | { LP8860_EEPROM_UNLOCK, 0x00}, |
325 | }; |
326 | |
327 | static const struct regmap_config lp8860_regmap_config = { |
328 | .reg_bits = 8, |
329 | .val_bits = 8, |
330 | |
331 | .max_register = LP8860_EEPROM_UNLOCK, |
332 | .reg_defaults = lp8860_reg_defs, |
333 | .num_reg_defaults = ARRAY_SIZE(lp8860_reg_defs), |
334 | .cache_type = REGCACHE_NONE, |
335 | }; |
336 | |
337 | static const struct reg_default lp8860_eeprom_defs[] = { |
338 | { LP8860_EEPROM_REG_0, 0x00 }, |
339 | { LP8860_EEPROM_REG_1, 0x00 }, |
340 | { LP8860_EEPROM_REG_2, 0x00 }, |
341 | { LP8860_EEPROM_REG_3, 0x00 }, |
342 | { LP8860_EEPROM_REG_4, 0x00 }, |
343 | { LP8860_EEPROM_REG_5, 0x00 }, |
344 | { LP8860_EEPROM_REG_6, 0x00 }, |
345 | { LP8860_EEPROM_REG_7, 0x00 }, |
346 | { LP8860_EEPROM_REG_8, 0x00 }, |
347 | { LP8860_EEPROM_REG_9, 0x00 }, |
348 | { LP8860_EEPROM_REG_10, 0x00 }, |
349 | { LP8860_EEPROM_REG_11, 0x00 }, |
350 | { LP8860_EEPROM_REG_12, 0x00 }, |
351 | { LP8860_EEPROM_REG_13, 0x00 }, |
352 | { LP8860_EEPROM_REG_14, 0x00 }, |
353 | { LP8860_EEPROM_REG_15, 0x00 }, |
354 | { LP8860_EEPROM_REG_16, 0x00 }, |
355 | { LP8860_EEPROM_REG_17, 0x00 }, |
356 | { LP8860_EEPROM_REG_18, 0x00 }, |
357 | { LP8860_EEPROM_REG_19, 0x00 }, |
358 | { LP8860_EEPROM_REG_20, 0x00 }, |
359 | { LP8860_EEPROM_REG_21, 0x00 }, |
360 | { LP8860_EEPROM_REG_22, 0x00 }, |
361 | { LP8860_EEPROM_REG_23, 0x00 }, |
362 | { LP8860_EEPROM_REG_24, 0x00 }, |
363 | }; |
364 | |
365 | static const struct regmap_config lp8860_eeprom_regmap_config = { |
366 | .reg_bits = 8, |
367 | .val_bits = 8, |
368 | |
369 | .max_register = LP8860_EEPROM_REG_24, |
370 | .reg_defaults = lp8860_eeprom_defs, |
371 | .num_reg_defaults = ARRAY_SIZE(lp8860_eeprom_defs), |
372 | .cache_type = REGCACHE_NONE, |
373 | }; |
374 | |
375 | static int lp8860_probe(struct i2c_client *client) |
376 | { |
377 | int ret; |
378 | struct lp8860_led *led; |
379 | struct device_node *np = dev_of_node(dev: &client->dev); |
380 | struct device_node *child_node; |
381 | struct led_init_data init_data = {}; |
382 | |
383 | led = devm_kzalloc(dev: &client->dev, size: sizeof(*led), GFP_KERNEL); |
384 | if (!led) |
385 | return -ENOMEM; |
386 | |
387 | child_node = of_get_next_available_child(node: np, NULL); |
388 | if (!child_node) |
389 | return -EINVAL; |
390 | |
391 | led->enable_gpio = devm_gpiod_get_optional(dev: &client->dev, |
392 | con_id: "enable" , flags: GPIOD_OUT_LOW); |
393 | if (IS_ERR(ptr: led->enable_gpio)) { |
394 | ret = PTR_ERR(ptr: led->enable_gpio); |
395 | dev_err(&client->dev, "Failed to get enable gpio: %d\n" , ret); |
396 | return ret; |
397 | } |
398 | |
399 | led->regulator = devm_regulator_get(dev: &client->dev, id: "vled" ); |
400 | if (IS_ERR(ptr: led->regulator)) |
401 | led->regulator = NULL; |
402 | |
403 | led->client = client; |
404 | led->led_dev.brightness_set_blocking = lp8860_brightness_set; |
405 | |
406 | mutex_init(&led->lock); |
407 | |
408 | i2c_set_clientdata(client, data: led); |
409 | |
410 | led->regmap = devm_regmap_init_i2c(client, &lp8860_regmap_config); |
411 | if (IS_ERR(ptr: led->regmap)) { |
412 | ret = PTR_ERR(ptr: led->regmap); |
413 | dev_err(&client->dev, "Failed to allocate register map: %d\n" , |
414 | ret); |
415 | return ret; |
416 | } |
417 | |
418 | led->eeprom_regmap = devm_regmap_init_i2c(client, &lp8860_eeprom_regmap_config); |
419 | if (IS_ERR(ptr: led->eeprom_regmap)) { |
420 | ret = PTR_ERR(ptr: led->eeprom_regmap); |
421 | dev_err(&client->dev, "Failed to allocate register map: %d\n" , |
422 | ret); |
423 | return ret; |
424 | } |
425 | |
426 | ret = lp8860_init(led); |
427 | if (ret) |
428 | return ret; |
429 | |
430 | init_data.fwnode = of_fwnode_handle(child_node); |
431 | init_data.devicename = LP8860_NAME; |
432 | init_data.default_label = ":display_cluster" ; |
433 | |
434 | ret = devm_led_classdev_register_ext(parent: &client->dev, led_cdev: &led->led_dev, |
435 | init_data: &init_data); |
436 | if (ret) { |
437 | dev_err(&client->dev, "led register err: %d\n" , ret); |
438 | return ret; |
439 | } |
440 | |
441 | return 0; |
442 | } |
443 | |
444 | static void lp8860_remove(struct i2c_client *client) |
445 | { |
446 | struct lp8860_led *led = i2c_get_clientdata(client); |
447 | int ret; |
448 | |
449 | gpiod_direction_output(desc: led->enable_gpio, value: 0); |
450 | |
451 | if (led->regulator) { |
452 | ret = regulator_disable(regulator: led->regulator); |
453 | if (ret) |
454 | dev_err(&led->client->dev, |
455 | "Failed to disable regulator\n" ); |
456 | } |
457 | |
458 | mutex_destroy(lock: &led->lock); |
459 | } |
460 | |
461 | static const struct i2c_device_id lp8860_id[] = { |
462 | { "lp8860" , 0 }, |
463 | { } |
464 | }; |
465 | MODULE_DEVICE_TABLE(i2c, lp8860_id); |
466 | |
467 | static const struct of_device_id of_lp8860_leds_match[] = { |
468 | { .compatible = "ti,lp8860" , }, |
469 | {}, |
470 | }; |
471 | MODULE_DEVICE_TABLE(of, of_lp8860_leds_match); |
472 | |
473 | static struct i2c_driver lp8860_driver = { |
474 | .driver = { |
475 | .name = "lp8860" , |
476 | .of_match_table = of_lp8860_leds_match, |
477 | }, |
478 | .probe = lp8860_probe, |
479 | .remove = lp8860_remove, |
480 | .id_table = lp8860_id, |
481 | }; |
482 | module_i2c_driver(lp8860_driver); |
483 | |
484 | MODULE_DESCRIPTION("Texas Instruments LP8860 LED driver" ); |
485 | MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>" ); |
486 | MODULE_LICENSE("GPL v2" ); |
487 | |