1 /*
2  * Copyright (c) 2020 Seagate Technology LLC
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/device.h>
8 #include <zephyr/devicetree.h>
9 #include <errno.h>
10 #include <zephyr/drivers/led.h>
11 #include <zephyr/sys/util.h>
12 #include <zephyr/kernel.h>
13 
14 #include <zephyr/logging/log.h>
15 LOG_MODULE_REGISTER(main, CONFIG_LOG_DEFAULT_LEVEL);
16 
17 #define LED_PWM_NODE_ID	 DT_COMPAT_GET_ANY_STATUS_OKAY(pwm_leds)
18 
19 const char *led_label[] = {
20 	DT_FOREACH_CHILD_SEP_VARGS(LED_PWM_NODE_ID, DT_PROP_OR, (,), label, NULL)
21 };
22 
23 #if CONFIG_BLINK_DELAY_AUTO_FALLBACK
24 /* Generic macro to get PWM period for each child */
25 #define GET_LED_PERIOD(node_id) DT_PWMS_PERIOD(node_id),
26 
27 /* Create array of PWM periods */
28 static const uint32_t led_periods_ns[] = {
29 	DT_FOREACH_CHILD(LED_PWM_NODE_ID, GET_LED_PERIOD)
30 };
31 
32 /**
33  * @brief Automatically calculate and apply a fallback blink delay for an LED
34  *
35  * This function calculates an appropriate blink delay based on the LED's PWM period
36  * when the requested delay exceeds the PWM hardware capabilities. The delay is
37  * calculated by dividing the PWM period by a divisor and ensuring a minimum
38  * visibility threshold.
39  *
40  * @param led_pwm LED PWM device
41  * @param led LED index to configure
42  * @param led_delay Pointer to store the calculated delay (in milliseconds)
43  * @param div Divisor to scale down the PWM period for delay calculation
44  *
45  * @return 0 on success, negative error code on failure from led_blink()
46  */
blink_delay_auto_fallback(const struct device * led_pwm,uint8_t led,uint32_t * led_delay,uint32_t div)47 static int blink_delay_auto_fallback(const struct device *led_pwm, uint8_t led,
48 					uint32_t *led_delay, uint32_t div)
49 {
50 	*led_delay = led_periods_ns[led] / 1000000 / div / 2;
51 	/* Ensure minimum 1ms for visibility */
52 	*led_delay = MAX(1, *led_delay);
53 
54 	LOG_INF("Auto-calculated delay: %u ms.", *led_delay);
55 
56 	return led_blink(led_pwm, led, *led_delay, *led_delay);
57 }
58 #endif
59 
60 const int num_leds = ARRAY_SIZE(led_label);
61 
62 #define MAX_BRIGHTNESS	100
63 
64 /**
65  * @brief Run tests on a single LED using the LED API syscalls.
66  *
67  * @param led_pwm LED PWM device.
68  * @param led Number of the LED to test.
69  */
run_led_test(const struct device * led_pwm,uint8_t led)70 static void run_led_test(const struct device *led_pwm, uint8_t led)
71 {
72 	int err;
73 	int16_t level;
74 	uint32_t led_delay;
75 
76 	LOG_INF("Testing LED %d - %s", led, led_label[led] ? : "no label");
77 
78 	/* Turn LED on. */
79 	err = led_on(led_pwm, led);
80 	if (err < 0) {
81 		LOG_ERR("err=%d", err);
82 		return;
83 	}
84 	LOG_INF("  Turned on");
85 	k_sleep(K_MSEC(1000));
86 
87 	/* Turn LED off. */
88 	err = led_off(led_pwm, led);
89 	if (err < 0) {
90 		LOG_ERR("err=%d", err);
91 		return;
92 	}
93 	LOG_INF("  Turned off");
94 	k_sleep(K_MSEC(1000));
95 
96 	/* Increase LED brightness gradually up to the maximum level. */
97 	LOG_INF("  Increasing brightness gradually");
98 	for (level = 0; level <= MAX_BRIGHTNESS; level++) {
99 		err = led_set_brightness(led_pwm, led, level);
100 		if (err < 0) {
101 			LOG_ERR("err=%d brightness=%d\n", err, level);
102 			return;
103 		}
104 		k_sleep(K_MSEC(CONFIG_FADE_DELAY));
105 	}
106 	k_sleep(K_MSEC(1000));
107 
108 	/* Decrease LED brightness gradually down to the minimum level. */
109 	LOG_INF("  Decreasing brightness gradually");
110 	for (level = MAX_BRIGHTNESS; level >= 0; level--) {
111 		err = led_set_brightness(led_pwm, led, level);
112 		if (err < 0) {
113 			LOG_ERR("err=%d brightness=%d\n", err, level);
114 			return;
115 		}
116 		k_sleep(K_MSEC(CONFIG_FADE_DELAY));
117 	}
118 	k_sleep(K_MSEC(1000));
119 
120 #if CONFIG_BLINK_DELAY_SHORT > 0
121 	led_delay = (uint32_t)CONFIG_BLINK_DELAY_SHORT;
122 	/* Start LED blinking (short cycle) */
123 	err = led_blink(led_pwm, led, led_delay, led_delay);
124 	if (err < 0) {
125 #if CONFIG_BLINK_DELAY_AUTO_FALLBACK && CONFIG_BLINK_DELAY_SHORT_LED_PERIOD_DIV > 0
126 		LOG_INF("CONFIG_BLINK_DELAY_SHORT (%u ms) exceeds PWM capabilities for LED %u.",
127 			led_delay, led);
128 		err = blink_delay_auto_fallback(led_pwm, led, &led_delay,
129 			(uint32_t)CONFIG_BLINK_DELAY_SHORT_LED_PERIOD_DIV);
130 #endif
131 		if (err < 0) {
132 			LOG_ERR("err=%d", err);
133 			return;
134 		}
135 	}
136 	LOG_INF("  Blinking on: %u msec, off: %u msec", led_delay, led_delay);
137 	k_sleep(K_MSEC(5000));
138 #endif
139 
140 #if CONFIG_BLINK_DELAY_LONG > 0
141 	led_delay = (uint32_t)CONFIG_BLINK_DELAY_LONG;
142 	/* Start LED blinking (long cycle) */
143 	err = led_blink(led_pwm, led, led_delay, led_delay);
144 	if (err < 0) {
145 #if CONFIG_BLINK_DELAY_AUTO_FALLBACK && CONFIG_BLINK_DELAY_LONG_LED_PERIOD_DIV > 0
146 		LOG_INF("CONFIG_BLINK_DELAY_LONG (%u ms) exceeds PWM capabilities for LED %u.",
147 			led_delay, led);
148 		err = blink_delay_auto_fallback(led_pwm, led, &led_delay,
149 			(uint32_t)CONFIG_BLINK_DELAY_LONG_LED_PERIOD_DIV);
150 #endif
151 		if (err < 0) {
152 			LOG_ERR("err=%d", err);
153 			LOG_INF("  Cycle period not supported - on: %u msec, "
154 				"off: %u msec", led_delay, led_delay);
155 		}
156 	}
157 	LOG_INF("  Blinking on: %u msec, off: %u msec", led_delay, led_delay);
158 	k_sleep(K_MSEC(5000));
159 #endif
160 
161 	/* Turn LED off. */
162 	err = led_off(led_pwm, led);
163 	if (err < 0) {
164 		LOG_ERR("err=%d", err);
165 		return;
166 	}
167 	LOG_INF("  Turned off, loop end");
168 }
169 
main(void)170 int main(void)
171 {
172 	const struct device *led_pwm;
173 	uint8_t led;
174 
175 	led_pwm = DEVICE_DT_GET(LED_PWM_NODE_ID);
176 	if (!device_is_ready(led_pwm)) {
177 		LOG_ERR("Device %s is not ready", led_pwm->name);
178 		return 0;
179 	}
180 
181 	if (!num_leds) {
182 		LOG_ERR("No LEDs found for %s", led_pwm->name);
183 		return 0;
184 	}
185 
186 	do {
187 		for (led = 0; led < num_leds; led++) {
188 			run_led_test(led_pwm, led);
189 		}
190 	} while (true);
191 	return 0;
192 }
193