From: Michael Polyntsov Date: Fri, 19 Jul 2024 09:12:12 +0000 (+0400) Subject: led: Implement software led blinking X-Git-Url: http://git.dujemihanovic.xyz/html/static/%7B%7B%20.RelPermalink%20%7D%7D?a=commitdiff_plain;h=b557f55e90f087ec310592b833441cd326206f61;p=u-boot.git led: Implement software led blinking If hardware (or driver) doesn't support leds blinking, it's now possible to use software implementation of blinking instead. This relies on cyclic functions. Signed-off-by: Michael Polyntsov Signed-off-by: Mikhail Kshevetskiy Reviewed-by: Simon Glass --- diff --git a/drivers/led/Kconfig b/drivers/led/Kconfig index 9837960198..bee74b2575 100644 --- a/drivers/led/Kconfig +++ b/drivers/led/Kconfig @@ -65,7 +65,7 @@ config LED_PWM Linux compatible ofdata. config LED_BLINK - bool "Support LED blinking" + bool "Support hardware LED blinking" depends on LED help Some drivers can support automatic blinking of LEDs with a given @@ -73,6 +73,20 @@ config LED_BLINK This option enables support for this which adds slightly to the code size. +config LED_SW_BLINK + bool "Support software LED blinking" + depends on LED + select CYCLIC + help + Turns on led blinking implemented in the software, useful when + the hardware doesn't support led blinking. Half of the period + led will be ON and the rest time it will be OFF. Standard + led commands can be used to configure blinking. Does nothing + if driver supports hardware blinking. + WARNING: Blinking may be inaccurate during execution of time + consuming commands (ex. flash reading). Also it completely + stops during OS booting. + config SPL_LED bool "Enable LED support in SPL" depends on SPL_DM diff --git a/drivers/led/Makefile b/drivers/led/Makefile index 2bcb858908..e27aa48848 100644 --- a/drivers/led/Makefile +++ b/drivers/led/Makefile @@ -4,6 +4,7 @@ # Written by Simon Glass obj-y += led-uclass.o +obj-$(CONFIG_LED_SW_BLINK) += led_sw_blink.o obj-$(CONFIG_LED_BCM6328) += led_bcm6328.o obj-$(CONFIG_LED_BCM6358) += led_bcm6358.o obj-$(CONFIG_LED_BCM6753) += led_bcm6753.o diff --git a/drivers/led/led-uclass.c b/drivers/led/led-uclass.c index 4932b90114..e6312d1dc4 100644 --- a/drivers/led/led-uclass.c +++ b/drivers/led/led-uclass.c @@ -58,6 +58,10 @@ int led_set_state(struct udevice *dev, enum led_state_t state) if (!ops->set_state) return -ENOSYS; + if (IS_ENABLED(CONFIG_LED_SW_BLINK) && + led_sw_on_state_change(dev, state)) + return 0; + return ops->set_state(dev, state); } @@ -68,6 +72,10 @@ enum led_state_t led_get_state(struct udevice *dev) if (!ops->get_state) return -ENOSYS; + if (IS_ENABLED(CONFIG_LED_SW_BLINK) && + led_sw_is_blinking(dev)) + return LEDST_BLINK; + return ops->get_state(dev); } @@ -80,6 +88,9 @@ int led_set_period(struct udevice *dev, int period_ms) return ops->set_period(dev, period_ms); #endif + if (IS_ENABLED(CONFIG_LED_SW_BLINK)) + return led_sw_set_period(dev, period_ms); + return -ENOSYS; } diff --git a/drivers/led/led_sw_blink.c b/drivers/led/led_sw_blink.c new file mode 100644 index 0000000000..9e36edbee4 --- /dev/null +++ b/drivers/led/led_sw_blink.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Software blinking helpers + * Copyright (C) 2024 IOPSYS Software Solutions AB + * Author: Mikhail Kshevetskiy + */ + +#include +#include +#include +#include + +#define CYCLIC_NAME_PREFIX "led_sw_blink_" + +static void led_sw_blink(struct cyclic_info *c) +{ + struct led_sw_blink *sw_blink; + struct udevice *dev; + struct led_ops *ops; + + sw_blink = container_of(c, struct led_sw_blink, cyclic); + dev = sw_blink->dev; + ops = led_get_ops(dev); + + switch (sw_blink->state) { + case LED_SW_BLINK_ST_OFF: + sw_blink->state = LED_SW_BLINK_ST_ON; + ops->set_state(dev, LEDST_ON); + break; + case LED_SW_BLINK_ST_ON: + sw_blink->state = LED_SW_BLINK_ST_OFF; + ops->set_state(dev, LEDST_OFF); + break; + case LED_SW_BLINK_ST_NOT_READY: + /* + * led_set_period has been called, but + * led_set_state(LDST_BLINK) has not yet, + * so doing nothing + */ + break; + default: + break; + } +} + +int led_sw_set_period(struct udevice *dev, int period_ms) +{ + struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev); + struct led_sw_blink *sw_blink = uc_plat->sw_blink; + struct led_ops *ops = led_get_ops(dev); + int half_period_us; + + half_period_us = period_ms * 1000 / 2; + + if (!sw_blink) { + int len = sizeof(struct led_sw_blink) + + strlen(CYCLIC_NAME_PREFIX) + + strlen(uc_plat->label) + 1; + + sw_blink = calloc(1, len); + if (!sw_blink) + return -ENOMEM; + + sw_blink->dev = dev; + sw_blink->state = LED_SW_BLINK_ST_DISABLED; + strcpy((char *)sw_blink->cyclic_name, CYCLIC_NAME_PREFIX); + strcat((char *)sw_blink->cyclic_name, uc_plat->label); + + uc_plat->sw_blink = sw_blink; + } + + if (sw_blink->state == LED_SW_BLINK_ST_DISABLED) { + cyclic_register(&sw_blink->cyclic, led_sw_blink, + half_period_us, sw_blink->cyclic_name); + } else { + sw_blink->cyclic.delay_us = half_period_us; + sw_blink->cyclic.start_time_us = timer_get_us(); + } + + sw_blink->state = LED_SW_BLINK_ST_NOT_READY; + ops->set_state(dev, LEDST_OFF); + + return 0; +} + +bool led_sw_is_blinking(struct udevice *dev) +{ + struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev); + struct led_sw_blink *sw_blink = uc_plat->sw_blink; + + if (!sw_blink) + return false; + + return sw_blink->state > LED_SW_BLINK_ST_NOT_READY; +} + +bool led_sw_on_state_change(struct udevice *dev, enum led_state_t state) +{ + struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev); + struct led_sw_blink *sw_blink = uc_plat->sw_blink; + + if (!sw_blink || sw_blink->state == LED_SW_BLINK_ST_DISABLED) + return false; + + if (state == LEDST_BLINK) { + /* start blinking on next led_sw_blink() call */ + sw_blink->state = LED_SW_BLINK_ST_OFF; + return true; + } + + /* stop blinking */ + uc_plat->sw_blink = NULL; + cyclic_unregister(&sw_blink->cyclic); + free(sw_blink); + + return false; +} diff --git a/include/led.h b/include/led.h index 9b24a4ce18..8270d6cef9 100644 --- a/include/led.h +++ b/include/led.h @@ -18,6 +18,20 @@ enum led_state_t { LEDST_COUNT, }; +enum led_sw_blink_state_t { + LED_SW_BLINK_ST_DISABLED, + LED_SW_BLINK_ST_NOT_READY, + LED_SW_BLINK_ST_OFF, + LED_SW_BLINK_ST_ON, +}; + +struct led_sw_blink { + enum led_sw_blink_state_t state; + struct udevice *dev; + struct cyclic_info cyclic; + const char cyclic_name[0]; +}; + /** * struct led_uc_plat - Platform data the uclass stores about each device * @@ -27,6 +41,9 @@ enum led_state_t { struct led_uc_plat { const char *label; enum led_state_t default_state; +#ifdef CONFIG_LED_SW_BLINK + struct led_sw_blink *sw_blink; +#endif }; /** @@ -116,4 +133,9 @@ int led_set_period(struct udevice *dev, int period_ms); */ int led_bind_generic(struct udevice *parent, const char *driver_name); +/* Internal functions for software blinking. Do not use them in your code */ +int led_sw_set_period(struct udevice *dev, int period_ms); +bool led_sw_is_blinking(struct udevice *dev); +bool led_sw_on_state_change(struct udevice *dev, enum led_state_t state); + #endif