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