Skip to content

drivers: led: Provide a fallback implementation of blink API #90237

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions drivers/led/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ zephyr_library_sources_ifdef(CONFIG_PCA9633 pca9633.c)
zephyr_library_sources_ifdef(CONFIG_TLC59108 tlc59108.c)
# zephyr-keep-sorted-stop

zephyr_library_sources(led_core.c)

zephyr_library_sources_ifdef(CONFIG_LED_SHELL led_shell.c)

zephyr_library_sources_ifdef(CONFIG_USERSPACE led_handlers.c)
6 changes: 6 additions & 0 deletions drivers/led/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ config LED_SHELL
help
Enable LED shell for testing.

config LED_BLINK_SOFTWARE
bool "LED Blink fallback"
help
Provides a software implementation of LED blink API.
This could be used when the LED controller doesn't support blinking.

# zephyr-keep-sorted-start
source "drivers/led/Kconfig.axp192"
source "drivers/led/Kconfig.dac"
Expand Down
38 changes: 38 additions & 0 deletions drivers/led/led_blink.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2025 BayLibre SAS
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef ZEPHYR_DRIVERS_LED_LED_BLINK_H_
#define ZEPHYR_DRIVERS_LED_LED_BLINK_H_

#define LED_BLINK_SOFTWARE_DATA_INIT(node_id) {}

#define LED_BLINK_SOFTWARE_DATA(inst, name) \
COND_CODE_1(CONFIG_LED_BLINK_SOFTWARE, ( \
.name = (struct led_blink_software_data[]) {\
DT_INST_FOREACH_CHILD_SEP(inst, LED_BLINK_SOFTWARE_DATA_INIT, (,)) \
},), ())

#ifdef CONFIG_LED_BLINK_SOFTWARE
struct led_blink_software_data {
const struct device *dev;
uint32_t led;
struct k_work_delayable work;
uint32_t delay_on;
uint32_t delay_off;
};

int led_blink_software_start(const struct device *dev, uint32_t led, uint32_t delay_on,
uint32_t delay_off);
#else
struct led_blink_software_data;
static inline int led_blink_software_start(const struct device *dev, uint32_t led,
uint32_t delay_on, uint32_t delay_off)
{
return -ENOSYS;
}
#endif

#endif /* ZEPHYR_DRIVERS_LED_LED_BLINK_H_ */
175 changes: 175 additions & 0 deletions drivers/led/led_core.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
* Copyright (c) 2025 BayLibre SAS
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/drivers/led.h>
#include <zephyr/kernel.h>

#include "led_blink.h"

static int led_core_on(const struct device *dev, uint32_t led)
{
const struct led_driver_api *api = (const struct led_driver_api *)dev->api;

if (api->set_brightness == NULL && api->on == NULL) {
return -ENOSYS;
}

if (api->on == NULL) {
return api->set_brightness(dev, led, LED_BRIGHTNESS_MAX);
}

return api->on(dev, led);
}

static int led_core_off(const struct device *dev, uint32_t led)
{
const struct led_driver_api *api = (const struct led_driver_api *)dev->api;

if (api->set_brightness == NULL && api->off == NULL) {
return -ENOSYS;
}

if (api->off == NULL) {
return api->set_brightness(dev, led, 0);
}

return api->off(dev, led);
}

#ifdef CONFIG_LED_BLINK_SOFTWARE
static void led_blink_software_off(struct k_work *work);
static struct led_blink_software_data *led_blink_software_get_data(const struct device *dev,
uint32_t led)
{
const struct led_driver_api *api = (const struct led_driver_api *)dev->api;

if (!api->get_blink_data) {
return NULL;
}

return api->get_blink_data(dev, led);
}

static void led_blink_software_on(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct led_blink_software_data *data =
CONTAINER_OF(dwork, struct led_blink_software_data, work);

led_core_on(data->dev, data->led);
k_work_init_delayable(&data->work, led_blink_software_off);
k_work_schedule(&data->work, K_MSEC(data->delay_on));
}

static void led_blink_software_off(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct led_blink_software_data *data =
CONTAINER_OF(dwork, struct led_blink_software_data, work);

led_core_off(data->dev, data->led);
k_work_init_delayable(&data->work, led_blink_software_on);
k_work_schedule(&data->work, K_MSEC(data->delay_off));
}

int led_blink_software_start(const struct device *dev, uint32_t led, uint32_t delay_on,
uint32_t delay_off)
{
struct led_blink_software_data *data;

data = led_blink_software_get_data(dev, led);
if (!data) {
return -EINVAL;
}

if (!delay_on && !delay_off) {
/* Default 1Hz blinking when delay_on and delay_off are 0 */
delay_on = 500;
delay_off = 500;
} else if (!delay_on) {
/* Always off */
led_core_off(dev, led);
} else if (!delay_off) {
/* Always on */
led_core_on(dev, led);
}

data->dev = dev;
data->led = led;
data->delay_on = delay_on;
data->delay_off = delay_off;

k_work_init_delayable(&data->work, led_blink_software_on);
return k_work_schedule(&data->work, K_NO_WAIT);
}

static void led_blink_software_stop(const struct device *dev, uint32_t led)
{
struct led_blink_software_data *data;

data = led_blink_software_get_data(dev, led);
if (data) {
struct k_work_sync sync;

k_work_cancel_delayable_sync(&data->work, &sync);
}
}
#else
static inline void led_blink_software_stop(const struct device *dev, uint32_t led)
{
}
#endif /* CONFIG_LED_BLINK_SOFTWARE */

int z_impl_led_on(const struct device *dev, uint32_t led)
{
led_blink_software_stop(dev, led);
return led_core_on(dev, led);
}

int z_impl_led_off(const struct device *dev, uint32_t led)
{
led_blink_software_stop(dev, led);
return led_core_off(dev, led);
}

int z_impl_led_set_brightness(const struct device *dev, uint32_t led, uint8_t value)
{
const struct led_driver_api *api = (const struct led_driver_api *)dev->api;

if (!value) {
led_blink_software_stop(dev, led);
}

if (api->set_brightness == NULL) {
if (api->on == NULL || api->off == NULL) {
return -ENOSYS;
}
}

if (value > LED_BRIGHTNESS_MAX) {
return -EINVAL;
}

if (api->set_brightness == NULL) {
if (value) {
return api->on(dev, led);
} else {
return api->off(dev, led);
}
}

return api->set_brightness(dev, led, value);
}

int z_impl_led_blink(const struct device *dev, uint32_t led, uint32_t delay_on, uint32_t delay_off)
{
const struct led_driver_api *api = (const struct led_driver_api *)dev->api;

if (api->blink == NULL) {
return led_blink_software_start(dev, led, delay_on, delay_off);
}
return api->blink(dev, led, delay_on, delay_off);
}
35 changes: 32 additions & 3 deletions drivers/led/led_gpio.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#include <zephyr/device.h>
#include <zephyr/kernel.h>

#include "led_blink.h"

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(led_gpio, CONFIG_LED_LOG_LEVEL);

Expand All @@ -24,9 +26,28 @@ struct led_gpio_config {
const struct gpio_dt_spec *led;
};

static int led_gpio_set_brightness(const struct device *dev, uint32_t led, uint8_t value)
struct led_gpio_data {
#ifdef CONFIG_LED_BLINK_SOFTWARE
struct led_blink_software_data *blink_data;
#endif
};

#ifdef CONFIG_LED_BLINK_SOFTWARE
struct led_blink_software_data *led_gpio_blink_data(const struct device *dev, uint32_t led)
{
const struct led_gpio_config *config = dev->config;
struct led_gpio_data *data = dev->data;

if (led >= config->num_leds) {
return NULL;
}

return &data->blink_data[led];
}
#endif

static int led_gpio_set_brightness(const struct device *dev, uint32_t led, uint8_t value)
{
const struct led_gpio_config *config = dev->config;
const struct gpio_dt_spec *led_gpio;

Expand Down Expand Up @@ -69,6 +90,9 @@ static int led_gpio_init(const struct device *dev)

static DEVICE_API(led, led_gpio_api) = {
.set_brightness = led_gpio_set_brightness,
#ifdef CONFIG_LED_BLINK_SOFTWARE
.get_blink_data = led_gpio_blink_data,
#endif
};

#define LED_GPIO_DEVICE(i) \
Expand All @@ -78,12 +102,17 @@ static const struct gpio_dt_spec gpio_dt_spec_##i[] = { \
}; \
\
static const struct led_gpio_config led_gpio_config_##i = { \
.num_leds = ARRAY_SIZE(gpio_dt_spec_##i), \
.num_leds = ARRAY_SIZE(gpio_dt_spec_##i), \
.led = gpio_dt_spec_##i, \
}; \
\
static struct led_gpio_data led_gpio_data_##i = { \
LED_BLINK_SOFTWARE_DATA(i, blink_data) \
}; \
\
DEVICE_DT_INST_DEFINE(i, &led_gpio_init, NULL, \
NULL, &led_gpio_config_##i, \
(&led_gpio_data_##i), \
&led_gpio_config_##i, \
POST_KERNEL, CONFIG_LED_INIT_PRIORITY, \
&led_gpio_api);

Expand Down
Loading