1 /*
2 * Copyright (c) 2018 Workaround GmbH
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT ti_lp5562
8
9 /**
10 * @file
11 * @brief LP5562 LED driver
12 *
13 * The LP5562 is a 4-channel LED driver that communicates over I2C. The four
14 * channels are expected to be connected to a red, green, blue and white LED.
15 * Each LED can be driven by two different sources.
16 *
17 * 1. The brightness of each LED can be configured directly by setting a
18 * register that drives the PWM of the connected LED.
19 *
20 * 2. A program can be transferred to the driver and run by one of the three
21 * available execution engines. Up to 16 commands can be defined in each
22 * program. Possible commands are:
23 * - Set the brightness.
24 * - Fade the brightness over time.
25 * - Loop parts of the program or the whole program.
26 * - Add delays.
27 * - Synchronize between the engines.
28 *
29 * After the program has been transferred, it can run infinitely without
30 * communication between the host MCU and the driver.
31 */
32
33 #include <zephyr/drivers/i2c.h>
34 #include <zephyr/drivers/led.h>
35 #include <zephyr/device.h>
36 #include <zephyr/kernel.h>
37
38 #define LOG_LEVEL CONFIG_LED_LOG_LEVEL
39 #include <zephyr/logging/log.h>
40 LOG_MODULE_REGISTER(lp5562);
41
42 #include "led_context.h"
43
44 /* Registers */
45 #define LP5562_ENABLE 0x00
46 #define LP5562_OP_MODE 0x01
47 #define LP5562_B_PWM 0x02
48 #define LP5562_G_PWM 0x03
49 #define LP5562_R_PWM 0x04
50 #define LP5562_B_CURRENT 0x05
51 #define LP5562_G_CURRENT 0x06
52 #define LP5562_R_CURRENT 0x07
53 #define LP5562_CONFIG 0x08
54 #define LP5562_ENG1_PC 0x09
55 #define LP5562_ENG2_PC 0x0A
56 #define LP5562_ENG3_PC 0x0B
57 #define LP5562_STATUS 0x0C
58 #define LP5562_RESET 0x0D
59 #define LP5562_W_PWM 0x0E
60 #define LP5562_W_CURRENT 0x0F
61 #define LP5562_PROG_MEM_ENG1_BASE 0x10
62 #define LP5562_PROG_MEM_ENG2_BASE 0x30
63 #define LP5562_PROG_MEM_ENG3_BASE 0x50
64 #define LP5562_LED_MAP 0x70
65
66 /*
67 * The wait command has six bits for the number of steps (max 63) with up to
68 * 15.6ms per step if the prescaler is set to 1. We round the step length
69 * however to 16ms for easier handling, so the maximum blinking period is
70 * therefore (16 * 63) = 1008ms. We round it down to 1000ms to be on the safe
71 * side.
72 */
73 #define LP5562_MAX_BLINK_PERIOD 1000
74 /*
75 * The minimum waiting period is 0.49ms with the prescaler set to 0 and one
76 * step. We round up to a full millisecond.
77 */
78 #define LP5562_MIN_BLINK_PERIOD 1
79
80 /* Brightness limits in percent */
81 #define LP5562_MIN_BRIGHTNESS 0
82 #define LP5562_MAX_BRIGHTNESS 100
83
84 /* Values for ENABLE register. */
85 #define LP5562_ENABLE_CHIP_EN (1 << 6)
86 #define LP5562_ENABLE_LOG_EN (1 << 7)
87
88 /* Values for CONFIG register. */
89 #define LP5562_CONFIG_EXTERNAL_CLOCK 0x00
90 #define LP5562_CONFIG_INTERNAL_CLOCK 0x01
91 #define LP5562_CONFIG_CLOCK_AUTOMATIC_SELECT 0x02
92 #define LP5562_CONFIG_PWRSAVE_EN (1 << 5)
93 /* Enable 558 Hz frequency for PWM. Default is 256. */
94 #define LP5562_CONFIG_PWM_HW_FREQ_558 (1 << 6)
95
96 /* Values for execution engine programs. */
97 #define LP5562_PROG_COMMAND_SET_PWM (1 << 6)
98 #define LP5562_PROG_COMMAND_RAMP_TIME(prescale, step_time) \
99 (((prescale) << 6) | (step_time))
100 #define LP5562_PROG_COMMAND_STEP_COUNT(fade_direction, count) \
101 (((fade_direction) << 7) | (count))
102
103 /* Helper definitions. */
104 #define LP5562_PROG_MAX_COMMANDS 16
105 #define LP5562_MASK 0x03
106 #define LP5562_CHANNEL_MASK(channel) ((LP5562_MASK) << (channel << 1))
107
108 /*
109 * Available channels. There are four LED channels usable with the LP5562. While
110 * they can be mapped to LEDs of any color, the driver's typical application is
111 * with a red, a green, a blue and a white LED. Since the data sheet's
112 * nomenclature uses RGBW, we keep it that way.
113 */
114 enum lp5562_led_channels {
115 LP5562_CHANNEL_B,
116 LP5562_CHANNEL_G,
117 LP5562_CHANNEL_R,
118 LP5562_CHANNEL_W,
119
120 LP5562_CHANNEL_COUNT,
121 };
122
123 /*
124 * Each channel can be driven by directly assigning a value between 0 and 255 to
125 * it to drive the PWM or by one of the three execution engines that can be
126 * programmed for custom lighting patterns in order to reduce the I2C traffic
127 * for repetitive patterns.
128 */
129 enum lp5562_led_sources {
130 LP5562_SOURCE_PWM,
131 LP5562_SOURCE_ENGINE_1,
132 LP5562_SOURCE_ENGINE_2,
133 LP5562_SOURCE_ENGINE_3,
134
135 LP5562_SOURCE_COUNT,
136 };
137
138 /* Operational modes of the execution engines. */
139 enum lp5562_engine_op_modes {
140 LP5562_OP_MODE_DISABLED = 0x00,
141 LP5562_OP_MODE_LOAD = 0x01,
142 LP5562_OP_MODE_RUN = 0x02,
143 LP5562_OP_MODE_DIRECT_CTRL = 0x03,
144 };
145
146 /* Execution state of the engines. */
147 enum lp5562_engine_exec_states {
148 LP5562_ENGINE_MODE_HOLD = 0x00,
149 LP5562_ENGINE_MODE_STEP = 0x01,
150 LP5562_ENGINE_MODE_RUN = 0x02,
151 LP5562_ENGINE_MODE_EXEC = 0x03,
152 };
153
154 /* Fading directions for programs executed by the engines. */
155 enum lp5562_engine_fade_dirs {
156 LP5562_FADE_UP = 0x00,
157 LP5562_FADE_DOWN = 0x01,
158 };
159
160 struct lp5562_config {
161 struct i2c_dt_spec bus;
162 };
163
164 struct lp5562_data {
165 struct led_data dev_data;
166 };
167
168 /*
169 * @brief Get the register for the given LED channel used to directly write a
170 * brightness value instead of using the execution engines.
171 *
172 * @param channel LED channel.
173 * @param reg Pointer to the register address.
174 *
175 * @retval 0 On success.
176 * @retval -EINVAL If an invalid channel is given.
177 */
lp5562_get_pwm_reg(enum lp5562_led_channels channel,uint8_t * reg)178 static int lp5562_get_pwm_reg(enum lp5562_led_channels channel, uint8_t *reg)
179 {
180 switch (channel) {
181 case LP5562_CHANNEL_W:
182 *reg = LP5562_W_PWM;
183 break;
184 case LP5562_CHANNEL_R:
185 *reg = LP5562_R_PWM;
186 break;
187 case LP5562_CHANNEL_G:
188 *reg = LP5562_G_PWM;
189 break;
190 case LP5562_CHANNEL_B:
191 *reg = LP5562_B_PWM;
192 break;
193 default:
194 LOG_ERR("Invalid channel given.");
195 return -EINVAL;
196 }
197
198 return 0;
199 }
200
201 /*
202 * @brief Get the base address for programs of the given execution engine.
203 *
204 * @param engine Engine the base address is requested for.
205 * @param base_addr Pointer to the base address.
206 *
207 * @retval 0 On success.
208 * @retval -EINVAL If a source is given that is not a valid engine.
209 */
lp5562_get_engine_ram_base_addr(enum lp5562_led_sources engine,uint8_t * base_addr)210 static int lp5562_get_engine_ram_base_addr(enum lp5562_led_sources engine,
211 uint8_t *base_addr)
212 {
213 switch (engine) {
214 case LP5562_SOURCE_ENGINE_1:
215 *base_addr = LP5562_PROG_MEM_ENG1_BASE;
216 break;
217 case LP5562_SOURCE_ENGINE_2:
218 *base_addr = LP5562_PROG_MEM_ENG2_BASE;
219 break;
220 case LP5562_SOURCE_ENGINE_3:
221 *base_addr = LP5562_PROG_MEM_ENG3_BASE;
222 break;
223 default:
224 return -EINVAL;
225 }
226
227 return 0;
228 }
229
230 /*
231 * @brief Helper to get the register bit shift for the execution engines.
232 *
233 * The engine with the highest index is placed on the lowest two bits in the
234 * OP_MODE and ENABLE registers.
235 *
236 * @param engine Engine the shift is requested for.
237 * @param shift Pointer to the shift value.
238 *
239 * @retval 0 On success.
240 * @retval -EINVAL If a source is given that is not a valid engine.
241 */
lp5562_get_engine_reg_shift(enum lp5562_led_sources engine,uint8_t * shift)242 static int lp5562_get_engine_reg_shift(enum lp5562_led_sources engine,
243 uint8_t *shift)
244 {
245 switch (engine) {
246 case LP5562_SOURCE_ENGINE_1:
247 *shift = 4U;
248 break;
249 case LP5562_SOURCE_ENGINE_2:
250 *shift = 2U;
251 break;
252 case LP5562_SOURCE_ENGINE_3:
253 *shift = 0U;
254 break;
255 default:
256 return -EINVAL;
257 }
258
259 return 0;
260 }
261
262 /*
263 * @brief Convert a time in milliseconds to a combination of prescale and
264 * step_time for the execution engine programs.
265 *
266 * This function expects the given time in milliseconds to be in the allowed
267 * range the device can handle (0ms to 1000ms).
268 *
269 * @param data Capabilities of the driver.
270 * @param ms Time to be converted in milliseconds [0..1000].
271 * @param prescale Pointer to the prescale value.
272 * @param step_time Pointer to the step_time value.
273 */
lp5562_ms_to_prescale_and_step(struct led_data * data,uint32_t ms,uint8_t * prescale,uint8_t * step_time)274 static void lp5562_ms_to_prescale_and_step(struct led_data *data, uint32_t ms,
275 uint8_t *prescale, uint8_t *step_time)
276 {
277 /*
278 * One step with the prescaler set to 0 takes 0.49ms. The max value for
279 * step_time is 63, so we just double the millisecond value. That way
280 * the step_time value never goes above the allowed 63.
281 */
282 if (ms < 31) {
283 *prescale = 0U;
284 *step_time = ms << 1;
285
286 return;
287 }
288
289 /*
290 * With a prescaler value set to 1 one step takes 15.6ms. So by dividing
291 * through 16 we get a decent enough result with low effort.
292 */
293 *prescale = 1U;
294 *step_time = ms >> 4;
295
296 return;
297 }
298
299 /*
300 * @brief Assign a source to the given LED channel.
301 *
302 * @param dev LP5562 device.
303 * @param channel LED channel the source is assigned to.
304 * @param source Source for the channel.
305 *
306 * @retval 0 On success.
307 * @retval -EIO If the underlying I2C call fails.
308 */
lp5562_set_led_source(const struct device * dev,enum lp5562_led_channels channel,enum lp5562_led_sources source)309 static int lp5562_set_led_source(const struct device *dev,
310 enum lp5562_led_channels channel,
311 enum lp5562_led_sources source)
312 {
313 const struct lp5562_config *config = dev->config;
314
315 if (i2c_reg_update_byte_dt(&config->bus, LP5562_LED_MAP,
316 LP5562_CHANNEL_MASK(channel),
317 source << (channel << 1))) {
318 LOG_ERR("LED reg update failed.");
319 return -EIO;
320 }
321
322 return 0;
323 }
324
325 /*
326 * @brief Get the assigned source of the given LED channel.
327 *
328 * @param dev LP5562 device.
329 * @param channel Requested LED channel.
330 * @param source Pointer to the source of the channel.
331 *
332 * @retval 0 On success.
333 * @retval -EIO If the underlying I2C call fails.
334 */
lp5562_get_led_source(const struct device * dev,enum lp5562_led_channels channel,enum lp5562_led_sources * source)335 static int lp5562_get_led_source(const struct device *dev,
336 enum lp5562_led_channels channel,
337 enum lp5562_led_sources *source)
338 {
339 const struct lp5562_config *config = dev->config;
340 uint8_t led_map;
341
342 if (i2c_reg_read_byte_dt(&config->bus, LP5562_LED_MAP, &led_map)) {
343 return -EIO;
344 }
345
346 *source = (led_map >> (channel << 1)) & LP5562_MASK;
347
348 return 0;
349 }
350
351 /*
352 * @brief Request whether an engine is currently running.
353 *
354 * @param dev LP5562 device.
355 * @param engine Engine to check.
356 *
357 * @return Indication of the engine execution state.
358 *
359 * @retval true If the engine is currently running.
360 * @retval false If the engine is not running or an error occurred.
361 */
lp5562_is_engine_executing(const struct device * dev,enum lp5562_led_sources engine)362 static bool lp5562_is_engine_executing(const struct device *dev,
363 enum lp5562_led_sources engine)
364 {
365 const struct lp5562_config *config = dev->config;
366 uint8_t enabled, shift;
367 int ret;
368
369 ret = lp5562_get_engine_reg_shift(engine, &shift);
370 if (ret) {
371 return false;
372 }
373
374 if (i2c_reg_read_byte_dt(&config->bus, LP5562_ENABLE, &enabled)) {
375 LOG_ERR("Failed to read ENABLE register.");
376 return false;
377 }
378
379 enabled = (enabled >> shift) & LP5562_MASK;
380
381 if (enabled == LP5562_ENGINE_MODE_RUN) {
382 return true;
383 }
384
385 return false;
386 }
387
388 /*
389 * @brief Get an available execution engine that is currently unused.
390 *
391 * @param dev LP5562 device.
392 * @param engine Pointer to the engine ID.
393 *
394 * @retval 0 On success.
395 * @retval -ENODEV If all engines are busy.
396 */
lp5562_get_available_engine(const struct device * dev,enum lp5562_led_sources * engine)397 static int lp5562_get_available_engine(const struct device *dev,
398 enum lp5562_led_sources *engine)
399 {
400 enum lp5562_led_sources src;
401
402 for (src = LP5562_SOURCE_ENGINE_1; src < LP5562_SOURCE_COUNT; src++) {
403 if (!lp5562_is_engine_executing(dev, src)) {
404 LOG_DBG("Available engine: %d", src);
405 *engine = src;
406 return 0;
407 }
408 }
409
410 LOG_ERR("No unused engine available");
411
412 return -ENODEV;
413 }
414
415 /*
416 * @brief Set an register shifted for the given execution engine.
417 *
418 * @param dev LP5562 device.
419 * @param engine Engine the value is shifted for.
420 * @param reg Register address to set.
421 * @param val Value to set.
422 *
423 * @retval 0 On success.
424 * @retval -EIO If the underlying I2C call fails.
425 */
lp5562_set_engine_reg(const struct device * dev,enum lp5562_led_sources engine,uint8_t reg,uint8_t val)426 static int lp5562_set_engine_reg(const struct device *dev,
427 enum lp5562_led_sources engine,
428 uint8_t reg, uint8_t val)
429 {
430 const struct lp5562_config *config = dev->config;
431 uint8_t shift;
432 int ret;
433
434 ret = lp5562_get_engine_reg_shift(engine, &shift);
435 if (ret) {
436 return ret;
437 }
438
439 if (i2c_reg_update_byte_dt(&config->bus, reg, LP5562_MASK << shift,
440 val << shift)) {
441 return -EIO;
442 }
443
444 return 0;
445 }
446
447 /*
448 * @brief Set the operational mode of the given engine.
449 *
450 * @param dev LP5562 device.
451 * @param engine Engine the operational mode is changed for.
452 * @param mode Mode to set.
453 *
454 * @retval 0 On success.
455 * @retval -EIO If the underlying I2C call fails.
456 */
lp5562_set_engine_op_mode(const struct device * dev,enum lp5562_led_sources engine,enum lp5562_engine_op_modes mode)457 static inline int lp5562_set_engine_op_mode(const struct device *dev,
458 enum lp5562_led_sources engine,
459 enum lp5562_engine_op_modes mode)
460 {
461 return lp5562_set_engine_reg(dev, engine, LP5562_OP_MODE, mode);
462 }
463
464 /*
465 * @brief Set the execution state of the given engine.
466 *
467 * @param dev LP5562 device.
468 * @param engine Engine the execution state is changed for.
469 * @param state State to set.
470 *
471 * @retval 0 On success.
472 * @retval -EIO If the underlying I2C call fails.
473 */
lp5562_set_engine_exec_state(const struct device * dev,enum lp5562_led_sources engine,enum lp5562_engine_exec_states state)474 static inline int lp5562_set_engine_exec_state(const struct device *dev,
475 enum lp5562_led_sources engine,
476 enum lp5562_engine_exec_states state)
477 {
478 int ret;
479
480 ret = lp5562_set_engine_reg(dev, engine, LP5562_ENABLE, state);
481
482 /*
483 * Delay between consecutive I2C writes to
484 * ENABLE register (00h) need to be longer than 488μs (typ.).
485 */
486 k_sleep(K_MSEC(1));
487
488 return ret;
489 }
490
491 /*
492 * @brief Start the execution of the program of the given engine.
493 *
494 * @param dev LP5562 device.
495 * @param engine Engine that is started.
496 *
497 * @retval 0 On success.
498 * @retval -EIO If the underlying I2C call fails.
499 */
lp5562_start_program_exec(const struct device * dev,enum lp5562_led_sources engine)500 static inline int lp5562_start_program_exec(const struct device *dev,
501 enum lp5562_led_sources engine)
502 {
503 if (lp5562_set_engine_op_mode(dev, engine, LP5562_OP_MODE_RUN)) {
504 return -EIO;
505 }
506
507 return lp5562_set_engine_exec_state(dev, engine,
508 LP5562_ENGINE_MODE_RUN);
509 }
510
511 /*
512 * @brief Stop the execution of the program of the given engine.
513 *
514 * @param dev LP5562 device.
515 * @param engine Engine that is stopped.
516 *
517 * @retval 0 On success.
518 * @retval -EIO If the underlying I2C call fails.
519 */
lp5562_stop_program_exec(const struct device * dev,enum lp5562_led_sources engine)520 static inline int lp5562_stop_program_exec(const struct device *dev,
521 enum lp5562_led_sources engine)
522 {
523 if (lp5562_set_engine_op_mode(dev, engine, LP5562_OP_MODE_DISABLED)) {
524 return -EIO;
525 }
526
527 return lp5562_set_engine_exec_state(dev, engine,
528 LP5562_ENGINE_MODE_HOLD);
529 }
530
531 /*
532 * @brief Program a command to the memory of the given execution engine.
533 *
534 * @param dev LP5562 device.
535 * @param engine Engine that is programmed.
536 * @param command_index Index of the command that is programmed.
537 * @param command_msb Most significant byte of the command.
538 * @param command_lsb Least significant byte of the command.
539 *
540 * @retval 0 On success.
541 * @retval -EINVAL If the given command index is out of range or an invalid
542 * engine is passed.
543 * @retval -EIO If the underlying I2C call fails.
544 */
lp5562_program_command(const struct device * dev,enum lp5562_led_sources engine,uint8_t command_index,uint8_t command_msb,uint8_t command_lsb)545 static int lp5562_program_command(const struct device *dev,
546 enum lp5562_led_sources engine,
547 uint8_t command_index,
548 uint8_t command_msb,
549 uint8_t command_lsb)
550 {
551 const struct lp5562_config *config = dev->config;
552 uint8_t prog_base_addr;
553 int ret;
554
555 if (command_index >= LP5562_PROG_MAX_COMMANDS) {
556 return -EINVAL;
557 }
558
559 ret = lp5562_get_engine_ram_base_addr(engine, &prog_base_addr);
560 if (ret) {
561 LOG_ERR("Failed to get base RAM address.");
562 return ret;
563 }
564
565 if (i2c_reg_write_byte_dt(&config->bus,
566 prog_base_addr + (command_index << 1),
567 command_msb)) {
568 LOG_ERR("Failed to update LED.");
569 return -EIO;
570 }
571
572 if (i2c_reg_write_byte_dt(&config->bus,
573 prog_base_addr + (command_index << 1) + 1,
574 command_lsb)) {
575 LOG_ERR("Failed to update LED.");
576 return -EIO;
577 }
578
579 return 0;
580 }
581
582 /*
583 * @brief Program a command to set a fixed brightness to the given engine.
584 *
585 * @param dev LP5562 device.
586 * @param engine Engine to be programmed.
587 * @param command_index Index of the command in the program sequence.
588 * @param brightness Brightness to be set for the LED in percent.
589 *
590 * @retval 0 On success.
591 * @retval -EINVAL If the passed arguments are invalid or out of range.
592 * @retval -EIO If the underlying I2C call fails.
593 */
lp5562_program_set_brightness(const struct device * dev,enum lp5562_led_sources engine,uint8_t command_index,uint8_t brightness)594 static int lp5562_program_set_brightness(const struct device *dev,
595 enum lp5562_led_sources engine,
596 uint8_t command_index,
597 uint8_t brightness)
598 {
599 struct lp5562_data *data = dev->data;
600 struct led_data *dev_data = &data->dev_data;
601 uint8_t val;
602
603 if ((brightness < dev_data->min_brightness) ||
604 (brightness > dev_data->max_brightness)) {
605 return -EINVAL;
606 }
607
608 val = (brightness * 0xFF) / dev_data->max_brightness;
609
610 return lp5562_program_command(dev, engine, command_index,
611 LP5562_PROG_COMMAND_SET_PWM, val);
612 }
613
614 /*
615 * @brief Program a command to ramp the brightness over time.
616 *
617 * In each step the PWM value is increased or decreased by 1/255th until the
618 * maximum or minimum value is reached or step_count steps have been done.
619 *
620 * @param dev LP5562 device.
621 * @param engine Engine to be programmed.
622 * @param command_index Index of the command in the program sequence.
623 * @param time_per_step Time each step takes in milliseconds.
624 * @param step_count Number of steps to perform.
625 * @param fade_dir Direction of the ramp (in-/decrease brightness).
626 *
627 * @retval 0 On success.
628 * @retval -EINVAL If the passed arguments are invalid or out of range.
629 * @retval -EIO If the underlying I2C call fails.
630 */
lp5562_program_ramp(const struct device * dev,enum lp5562_led_sources engine,uint8_t command_index,uint32_t time_per_step,uint8_t step_count,enum lp5562_engine_fade_dirs fade_dir)631 static int lp5562_program_ramp(const struct device *dev,
632 enum lp5562_led_sources engine,
633 uint8_t command_index,
634 uint32_t time_per_step,
635 uint8_t step_count,
636 enum lp5562_engine_fade_dirs fade_dir)
637 {
638 struct lp5562_data *data = dev->data;
639 struct led_data *dev_data = &data->dev_data;
640 uint8_t prescale, step_time;
641
642 if ((time_per_step < dev_data->min_period) ||
643 (time_per_step > dev_data->max_period)) {
644 return -EINVAL;
645 }
646
647 lp5562_ms_to_prescale_and_step(dev_data, time_per_step,
648 &prescale, &step_time);
649
650 return lp5562_program_command(dev, engine, command_index,
651 LP5562_PROG_COMMAND_RAMP_TIME(prescale, step_time),
652 LP5562_PROG_COMMAND_STEP_COUNT(fade_dir, step_count));
653 }
654
655 /*
656 * @brief Program a command to do nothing for the given time.
657 *
658 * @param dev LP5562 device.
659 * @param engine Engine to be programmed.
660 * @param command_index Index of the command in the program sequence.
661 * @param time Time to do nothing in milliseconds.
662 *
663 * @retval 0 On success.
664 * @retval -EINVAL If the passed arguments are invalid or out of range.
665 * @retval -EIO If the underlying I2C call fails.
666 */
lp5562_program_wait(const struct device * dev,enum lp5562_led_sources engine,uint8_t command_index,uint32_t time)667 static inline int lp5562_program_wait(const struct device *dev,
668 enum lp5562_led_sources engine,
669 uint8_t command_index,
670 uint32_t time)
671 {
672 /*
673 * A wait command is a ramp with the step_count set to 0. The fading
674 * direction does not matter in this case.
675 */
676 return lp5562_program_ramp(dev, engine, command_index,
677 time, 0, LP5562_FADE_UP);
678 }
679
680 /*
681 * @brief Program a command to go back to the beginning of the program.
682 *
683 * Can be used at the end of a program to loop it infinitely.
684 *
685 * @param dev LP5562 device.
686 * @param engine Engine to be programmed.
687 * @param command_index Index of the command in the program sequence.
688 *
689 * @retval 0 On success.
690 * @retval -EINVAL If the given command index is out of range or an invalid
691 * engine is passed.
692 * @retval -EIO If the underlying I2C call fails.
693 */
lp5562_program_go_to_start(const struct device * dev,enum lp5562_led_sources engine,uint8_t command_index)694 static inline int lp5562_program_go_to_start(const struct device *dev,
695 enum lp5562_led_sources engine,
696 uint8_t command_index)
697 {
698 return lp5562_program_command(dev, engine, command_index, 0x00, 0x00);
699 }
700
701 /*
702 * @brief Change the brightness of a running blink program.
703 *
704 * We know that the current program executes a blinking pattern
705 * consisting of following commands:
706 *
707 * - set_brightness high
708 * - wait on_delay
709 * - set_brightness low
710 * - wait off_delay
711 * - return to start
712 *
713 * In order to change the brightness during blinking, we overwrite only
714 * the first command and start execution again.
715 *
716 * @param dev LP5562 device.
717 * @param engine Engine running the blinking program.
718 * @param brightness_on New brightness value.
719 *
720 * @retval 0 On Success.
721 * @retval -EINVAL If the engine ID or brightness is out of range.
722 * @retval -EIO If the underlying I2C call fails.
723 */
lp5562_update_blinking_brightness(const struct device * dev,enum lp5562_led_sources engine,uint8_t brightness_on)724 static int lp5562_update_blinking_brightness(const struct device *dev,
725 enum lp5562_led_sources engine,
726 uint8_t brightness_on)
727 {
728 int ret;
729
730 ret = lp5562_stop_program_exec(dev, engine);
731 if (ret) {
732 return ret;
733 }
734
735 ret = lp5562_set_engine_op_mode(dev, engine, LP5562_OP_MODE_LOAD);
736 if (ret) {
737 return ret;
738 }
739
740
741 ret = lp5562_program_set_brightness(dev, engine, 0, brightness_on);
742 if (ret) {
743 return ret;
744 }
745
746 ret = lp5562_start_program_exec(dev, engine);
747 if (ret) {
748 LOG_ERR("Failed to execute program.");
749 return ret;
750 }
751
752 return 0;
753 }
754
lp5562_led_blink(const struct device * dev,uint32_t led,uint32_t delay_on,uint32_t delay_off)755 static int lp5562_led_blink(const struct device *dev, uint32_t led,
756 uint32_t delay_on, uint32_t delay_off)
757 {
758 struct lp5562_data *data = dev->data;
759 struct led_data *dev_data = &data->dev_data;
760 int ret;
761 enum lp5562_led_sources engine;
762 uint8_t command_index = 0U;
763
764 ret = lp5562_get_available_engine(dev, &engine);
765 if (ret) {
766 return ret;
767 }
768
769 ret = lp5562_set_led_source(dev, led, engine);
770 if (ret) {
771 LOG_ERR("Failed to set LED source.");
772 return ret;
773 }
774
775 ret = lp5562_set_engine_op_mode(dev, engine, LP5562_OP_MODE_LOAD);
776 if (ret) {
777 return ret;
778 }
779
780 ret = lp5562_program_set_brightness(dev, engine, command_index,
781 dev_data->max_brightness);
782 if (ret) {
783 return ret;
784 }
785
786 ret = lp5562_program_wait(dev, engine, ++command_index, delay_on);
787 if (ret) {
788 return ret;
789 }
790
791 ret = lp5562_program_set_brightness(dev, engine, ++command_index,
792 dev_data->min_brightness);
793 if (ret) {
794 return ret;
795 }
796
797 ret = lp5562_program_wait(dev, engine, ++command_index, delay_off);
798 if (ret) {
799 return ret;
800 }
801
802 ret = lp5562_program_go_to_start(dev, engine, ++command_index);
803 if (ret) {
804 return ret;
805 }
806
807 ret = lp5562_start_program_exec(dev, engine);
808 if (ret) {
809 LOG_ERR("Failed to execute program.");
810 return ret;
811 }
812
813 return 0;
814 }
815
lp5562_led_set_brightness(const struct device * dev,uint32_t led,uint8_t value)816 static int lp5562_led_set_brightness(const struct device *dev, uint32_t led,
817 uint8_t value)
818 {
819 const struct lp5562_config *config = dev->config;
820 struct lp5562_data *data = dev->data;
821 struct led_data *dev_data = &data->dev_data;
822 int ret;
823 uint8_t val, reg;
824 enum lp5562_led_sources current_source;
825
826 if ((value < dev_data->min_brightness) ||
827 (value > dev_data->max_brightness)) {
828 return -EINVAL;
829 }
830
831 ret = lp5562_get_led_source(dev, led, ¤t_source);
832 if (ret) {
833 return ret;
834 }
835
836 if (current_source != LP5562_SOURCE_PWM) {
837 if (lp5562_is_engine_executing(dev, current_source)) {
838 /*
839 * LED is blinking currently. Restart the blinking with
840 * the passed brightness.
841 */
842 return lp5562_update_blinking_brightness(dev,
843 current_source, value);
844 }
845
846 ret = lp5562_set_led_source(dev, led, LP5562_SOURCE_PWM);
847 if (ret) {
848 return ret;
849 }
850 }
851
852 val = (value * 0xFF) / dev_data->max_brightness;
853
854 ret = lp5562_get_pwm_reg(led, ®);
855 if (ret) {
856 return ret;
857 }
858
859 if (i2c_reg_write_byte_dt(&config->bus, reg, val)) {
860 LOG_ERR("LED write failed");
861 return -EIO;
862 }
863
864 return 0;
865 }
866
lp5562_led_on(const struct device * dev,uint32_t led)867 static inline int lp5562_led_on(const struct device *dev, uint32_t led)
868 {
869 struct lp5562_data *data = dev->data;
870 struct led_data *dev_data = &data->dev_data;
871
872 return lp5562_led_set_brightness(dev, led, dev_data->max_brightness);
873 }
874
lp5562_led_off(const struct device * dev,uint32_t led)875 static inline int lp5562_led_off(const struct device *dev, uint32_t led)
876 {
877 struct lp5562_data *data = dev->data;
878 struct led_data *dev_data = &data->dev_data;
879
880 int ret;
881 enum lp5562_led_sources current_source;
882
883 ret = lp5562_get_led_source(dev, led, ¤t_source);
884 if (ret) {
885 return ret;
886 }
887
888 if (current_source != LP5562_SOURCE_PWM) {
889 ret = lp5562_stop_program_exec(dev, current_source);
890 if (ret) {
891 return ret;
892 }
893 }
894
895 return lp5562_led_set_brightness(dev, led, dev_data->min_brightness);
896 }
897
lp5562_led_init(const struct device * dev)898 static int lp5562_led_init(const struct device *dev)
899 {
900 const struct lp5562_config *config = dev->config;
901 struct lp5562_data *data = dev->data;
902 struct led_data *dev_data = &data->dev_data;
903
904 if (!device_is_ready(config->bus.bus)) {
905 LOG_ERR("I2C device not ready");
906 return -ENODEV;
907 }
908
909 /* Hardware specific limits */
910 dev_data->min_period = LP5562_MIN_BLINK_PERIOD;
911 dev_data->max_period = LP5562_MAX_BLINK_PERIOD;
912 dev_data->min_brightness = LP5562_MIN_BRIGHTNESS;
913 dev_data->max_brightness = LP5562_MAX_BRIGHTNESS;
914
915 if (i2c_reg_write_byte_dt(&config->bus, LP5562_ENABLE,
916 LP5562_ENABLE_CHIP_EN)) {
917 LOG_ERR("Enabling LP5562 LED chip failed.");
918 return -EIO;
919 }
920
921 if (i2c_reg_write_byte_dt(&config->bus, LP5562_CONFIG,
922 (LP5562_CONFIG_INTERNAL_CLOCK |
923 LP5562_CONFIG_PWRSAVE_EN))) {
924 LOG_ERR("Configuring LP5562 LED chip failed.");
925 return -EIO;
926 }
927
928 if (i2c_reg_write_byte_dt(&config->bus, LP5562_OP_MODE, 0x00)) {
929 LOG_ERR("Disabling all engines failed.");
930 return -EIO;
931 }
932
933 if (i2c_reg_write_byte_dt(&config->bus, LP5562_LED_MAP, 0x00)) {
934 LOG_ERR("Setting all LEDs to manual control failed.");
935 return -EIO;
936 }
937
938 return 0;
939 }
940
941 static const struct led_driver_api lp5562_led_api = {
942 .blink = lp5562_led_blink,
943 .set_brightness = lp5562_led_set_brightness,
944 .on = lp5562_led_on,
945 .off = lp5562_led_off,
946 };
947
948 #define LP5562_DEFINE(id) \
949 static const struct lp5562_config lp5562_config_##id = { \
950 .bus = I2C_DT_SPEC_INST_GET(id), \
951 }; \
952 \
953 struct lp5562_data lp5562_data_##id; \
954 DEVICE_DT_INST_DEFINE(id, &lp5562_led_init, NULL, \
955 &lp5562_data_##id, \
956 &lp5562_config_##id, POST_KERNEL, \
957 CONFIG_LED_INIT_PRIORITY, \
958 &lp5562_led_api); \
959
960 DT_INST_FOREACH_STATUS_OKAY(LP5562_DEFINE)
961