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