Skip to content

Commit 27baa49

Browse files
committed
drivers: led: Provide a fallback implementation of blink API
The LED blink API i squite useful but this is not supported by all the LED controller. This provides a software implementation of blink that use a delayable work to blink a LED. This could be enabled using Kconfig, as well the maximum number of LED that could blink at the same time using blink fallback. This does not requires any changes to driver to be used. Signed-off-by: Alexandre Bailon <abailon@baylibre.com>
1 parent 610a7e2 commit 27baa49

File tree

4 files changed

+172
-0
lines changed

4 files changed

+172
-0
lines changed

drivers/led/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,5 @@ zephyr_library_sources_ifdef(CONFIG_TLC59108 tlc59108.c)
2828
zephyr_library_sources_ifdef(CONFIG_LED_SHELL led_shell.c)
2929

3030
zephyr_library_sources_ifdef(CONFIG_USERSPACE led_handlers.c)
31+
32+
zephyr_library_sources_ifdef(CONFIG_LED_BLINK_FALLBACK blink_fallback.c)

drivers/led/Kconfig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,23 @@ config LED_SHELL
2626
help
2727
Enable LED shell for testing.
2828

29+
config LED_BLINK_FALLBACK
30+
bool "LED Blink fallback"
31+
help
32+
Provides a software implementation of LED blink API.
33+
This could be used when the LED controller doesn't support blinking.
34+
35+
if LED_BLINK_FALLBACK
36+
37+
config LED_BLINK_FALLBACK_COUNT
38+
int "Maximum of LED using blink fallback"
39+
default 3
40+
help
41+
This set the maximum number of LED that could blink
42+
in the same time using blink fallback.
43+
44+
endif # LED_BLINK_FALLBACK
45+
2946
# zephyr-keep-sorted-start
3047
source "drivers/led/Kconfig.axp192"
3148
source "drivers/led/Kconfig.dac"

drivers/led/blink_fallback.c

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright (c) 2025 BayLibre SAS
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/drivers/led.h>
8+
#include <zephyr/device.h>
9+
#include <zephyr/kernel.h>
10+
11+
struct led_blink_fallback_data {
12+
const struct device *dev;
13+
uint32_t led;
14+
struct k_work_delayable work;
15+
uint32_t delay_on;
16+
uint32_t delay_off;
17+
};
18+
19+
static void led_blink_fallback_off(struct k_work *work);
20+
21+
static K_MUTEX_DEFINE(led_blink_fallback_mutex);
22+
static struct led_blink_fallback_data blink_fallback_data[CONFIG_LED_BLINK_FALLBACK_COUNT];
23+
24+
static void led_blink_fallback_on(struct k_work *work)
25+
{
26+
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
27+
struct led_blink_fallback_data *data =
28+
CONTAINER_OF(dwork, struct led_blink_fallback_data, work);
29+
30+
const struct led_driver_api *api = (const struct led_driver_api *)data->dev->api;
31+
32+
if (api->on) {
33+
api->on(data->dev, data->led);
34+
} else {
35+
api->set_brightness(data->dev, data->led, LED_BRIGTHNESS_MAX);
36+
}
37+
k_work_init_delayable(&data->work, led_blink_fallback_off);
38+
k_work_schedule(&data->work, K_MSEC(data->delay_on));
39+
}
40+
41+
static void led_blink_fallback_off(struct k_work *work)
42+
{
43+
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
44+
struct led_blink_fallback_data *data =
45+
CONTAINER_OF(dwork, struct led_blink_fallback_data, work);
46+
47+
const struct led_driver_api *api = (const struct led_driver_api *)data->dev->api;
48+
49+
if (api->off) {
50+
api->off(data->dev, data->led);
51+
} else {
52+
api->set_brightness(data->dev, data->led, 0);
53+
}
54+
k_work_init_delayable(&data->work, led_blink_fallback_on);
55+
k_work_schedule(&data->work, K_MSEC(data->delay_off));
56+
}
57+
58+
static struct led_blink_fallback_data *led_fallback_get_data(const struct device *dev, uint32_t led)
59+
{
60+
/*
61+
* Find the first unused blink_fallback_data or
62+
* if the led is already bliking, return the currently used blink_fallback_data.
63+
*/
64+
for (int i = 0; i < CONFIG_LED_BLINK_FALLBACK_COUNT; i++) {
65+
struct led_blink_fallback_data *data = &blink_fallback_data[i];
66+
67+
if (data->dev == dev && data->led == led) {
68+
return data;
69+
}
70+
71+
if (data->dev == NULL) {
72+
return data;
73+
}
74+
}
75+
76+
return NULL;
77+
}
78+
79+
int led_blink_fallback(const struct device *dev, uint32_t led, uint32_t delay_on,
80+
uint32_t delay_off)
81+
{
82+
const struct led_driver_api *api = (const struct led_driver_api *)dev->api;
83+
struct led_blink_fallback_data *data;
84+
int ret = 0;
85+
86+
/* Make sure the expected API are supported before we use them */
87+
if (!api->set_brightness && (!api->on || !api->off)) {
88+
return -ENOSYS;
89+
}
90+
91+
k_mutex_lock(&led_blink_fallback_mutex, K_FOREVER);
92+
data = led_fallback_get_data(dev, led);
93+
if (!data) {
94+
ret = -ENOMEM;
95+
goto out;
96+
}
97+
98+
/* Stop blinking, more likely when we use led_on or led_off */
99+
if (delay_on == 0 && delay_off == 0) {
100+
struct k_work_sync sync;
101+
102+
if (!data->dev) {
103+
ret = -ENODEV;
104+
goto out;
105+
}
106+
107+
k_work_cancel_delayable_sync(&data->work, &sync);
108+
data->dev = NULL;
109+
goto out;
110+
}
111+
112+
/* Only update the timings if the LED is already blinking */
113+
if (data->dev) {
114+
data->delay_on = delay_on;
115+
data->delay_off = delay_off;
116+
goto out;
117+
}
118+
119+
data->dev = dev;
120+
data->led = led;
121+
data->delay_on = delay_on;
122+
data->delay_off = delay_off;
123+
124+
k_work_init_delayable(&data->work, led_blink_fallback_on);
125+
k_work_schedule(&data->work, K_NO_WAIT);
126+
127+
out:
128+
k_mutex_unlock(&led_blink_fallback_mutex);
129+
return ret;
130+
}

include/zephyr/drivers/led.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ __subsystem struct led_driver_api {
128128
led_api_write_channels write_channels;
129129
};
130130

131+
#ifdef CONFIG_LED_BLINK_FALLBACK
132+
int led_blink_fallback(const struct device *dev, uint32_t led, uint32_t delay_on,
133+
uint32_t delay_off);
134+
#endif
135+
131136
/**
132137
* @brief Blink an LED
133138
*
@@ -150,7 +155,11 @@ static inline int z_impl_led_blink(const struct device *dev, uint32_t led,
150155
(const struct led_driver_api *)dev->api;
151156

152157
if (api->blink == NULL) {
158+
#ifdef CONFIG_LED_BLINK_FALLBACK
159+
return led_blink_fallback(dev, led, delay_on, delay_off);
160+
#else
153161
return -ENOSYS;
162+
#endif
154163
}
155164
return api->blink(dev, led, delay_on, delay_off);
156165
}
@@ -331,6 +340,13 @@ static inline int z_impl_led_on(const struct device *dev, uint32_t led)
331340
const struct led_driver_api *api =
332341
(const struct led_driver_api *)dev->api;
333342

343+
#ifdef CONFIG_LED_BLINK_FALLBACK
344+
/* Stop blinking */
345+
if (api->blink == NULL) {
346+
led_blink_fallback(dev, led, 0, 0);
347+
}
348+
#endif
349+
334350
if (api->set_brightness == NULL && api->on == NULL) {
335351
return -ENOSYS;
336352
}
@@ -361,6 +377,13 @@ static inline int z_impl_led_off(const struct device *dev, uint32_t led)
361377
const struct led_driver_api *api =
362378
(const struct led_driver_api *)dev->api;
363379

380+
#ifdef CONFIG_LED_BLINK_FALLBACK
381+
/* Stop blinking */
382+
if (api->blink == NULL) {
383+
led_blink_fallback(dev, led, 0, 0);
384+
}
385+
#endif
386+
364387
if (api->set_brightness == NULL && api->off == NULL) {
365388
return -ENOSYS;
366389
}

0 commit comments

Comments
 (0)