diff --git a/drivers/led/CMakeLists.txt b/drivers/led/CMakeLists.txt index 73a808d7d5a2..aa1081cfa70d 100644 --- a/drivers/led/CMakeLists.txt +++ b/drivers/led/CMakeLists.txt @@ -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) diff --git a/drivers/led/Kconfig b/drivers/led/Kconfig index bac815242c39..c6e40becea77 100644 --- a/drivers/led/Kconfig +++ b/drivers/led/Kconfig @@ -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" diff --git a/drivers/led/led_blink.h b/drivers/led/led_blink.h new file mode 100644 index 000000000000..335f0a3c3bea --- /dev/null +++ b/drivers/led/led_blink.h @@ -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_ */ diff --git a/drivers/led/led_core.c b/drivers/led/led_core.c new file mode 100644 index 000000000000..a19e4ce96370 --- /dev/null +++ b/drivers/led/led_core.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2025 BayLibre SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#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); +} diff --git a/drivers/led/led_gpio.c b/drivers/led/led_gpio.c index 433dcca0dc51..f235ed5aac58 100644 --- a/drivers/led/led_gpio.c +++ b/drivers/led/led_gpio.c @@ -16,6 +16,8 @@ #include #include +#include "led_blink.h" + #include LOG_MODULE_REGISTER(led_gpio, CONFIG_LED_LOG_LEVEL); @@ -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; @@ -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) \ @@ -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); diff --git a/include/zephyr/drivers/led.h b/include/zephyr/drivers/led.h index 7f3a182d0a87..36d0905c5b84 100644 --- a/include/zephyr/drivers/led.h +++ b/include/zephyr/drivers/led.h @@ -60,6 +60,18 @@ struct led_info { typedef int (*led_api_blink)(const struct device *dev, uint32_t led, uint32_t delay_on, uint32_t delay_off); +/** + * @typedef led_api_get_blink_data() + * @brief Callback API for getting data used by software blinking + * + * @param dev LED device + * @param led LED number + * @return Pointer to the software blinking data structure for the specified LED, + * or NULL if not supported or in case of an error. + */ +typedef struct led_blink_software_data *(*led_api_get_blink_data)(const struct device *dev, + uint32_t led); + /** * @typedef led_api_get_info() * @brief Optional API callback to get LED information @@ -123,6 +135,7 @@ __subsystem struct led_driver_api { led_api_set_brightness set_brightness; /* Optional callbacks. */ led_api_blink blink; + led_api_get_blink_data get_blink_data; led_api_get_info get_info; led_api_set_color set_color; led_api_write_channels write_channels; @@ -143,18 +156,6 @@ __subsystem struct led_driver_api { __syscall int led_blink(const struct device *dev, uint32_t led, uint32_t delay_on, uint32_t delay_off); -static inline 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 -ENOSYS; - } - return api->blink(dev, led, delay_on, delay_off); -} - /** * @brief Get LED information * @@ -200,34 +201,6 @@ static inline int z_impl_led_get_info(const struct device *dev, uint32_t led, __syscall int led_set_brightness(const struct device *dev, uint32_t led, uint8_t value); -static inline 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 (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); -} - /** * @brief Write/update a strip of LED channels * @@ -327,22 +300,6 @@ static inline int z_impl_led_set_color(const struct device *dev, uint32_t led, */ __syscall int led_on(const struct device *dev, uint32_t led); -static inline int z_impl_led_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); -} - /** * @brief Turn off an LED * @@ -357,22 +314,6 @@ static inline int z_impl_led_on(const struct device *dev, uint32_t led) */ __syscall int led_off(const struct device *dev, uint32_t led); -static inline int z_impl_led_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); -} - /* * LED DT helpers. */