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, &current_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, &reg);
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, &current_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