On 05.07.2024 11:29, Mark Kettenis wrote: > [You don't often get email from mark.kette...@xs4all.nl. Learn why this is > important at https://aka.ms/LearnAboutSenderIdentification ] > >> From: Mikhail Kshevetskiy <mikhail.kshevets...@iopsys.eu> >> Date: Fri, 5 Jul 2024 06:26:24 +0400 >> >> From: Michael Polyntsov <michael.polynt...@iopsys.eu> >> >> 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. >> >> v2 changes: >> * Drop sw_blink_state structure, move its necessary fields to >> led_uc_plat structure. >> * Add cyclic_info pointer to led_uc_plat structure. This >> simplify code a lot. >> * Remove cyclic function search logic. Not needed anymore. >> * Fix blinking period. It was twice large. >> * Other cleanups. >> >> v3 changes: >> * Adapt code to recent cyclic function changes >> * Move software blinking functions to separate file >> * Other small changes >> >> Signed-off-by: Michael Polyntsov <michael.polynt...@iopsys.eu> >> Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevets...@iopsys.eu> >> --- >> drivers/led/Kconfig | 14 +++++ >> drivers/led/Makefile | 1 + >> drivers/led/led-uclass.c | 16 +++++- >> drivers/led/led_sw_blink.c | 106 +++++++++++++++++++++++++++++++++++++ >> include/led.h | 17 ++++++ >> 5 files changed, 152 insertions(+), 2 deletions(-) >> create mode 100644 drivers/led/led_sw_blink.c >> >> diff --git a/drivers/led/Kconfig b/drivers/led/Kconfig >> index 9837960198d..dc9d4c8a757 100644 >> --- a/drivers/led/Kconfig >> +++ b/drivers/led/Kconfig >> @@ -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_BLINK >> + 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 blinking. >> + WARNING: Blinking may be inaccurate during execution of time >> + consuming commands (ex. flash reading). Also it completely >> + stops during OS booting. > Doesn't that make this feature pretty much pointless?
It steel make sense if total time of blinking is much large than blinking period. For example this is a case of router rescue process. One should upload and flash the firmware. Usually it takes a lot of time. Vendors often wants led blinking to differ router rescue mode from normal boot. >> config SPL_LED >> bool "Enable LED support in SPL" >> depends on SPL_DM >> diff --git a/drivers/led/Makefile b/drivers/led/Makefile >> index 2bcb8589087..e27aa488482 100644 >> --- a/drivers/led/Makefile >> +++ b/drivers/led/Makefile >> @@ -4,6 +4,7 @@ >> # Written by Simon Glass <s...@chromium.org> >> >> 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 f37bf6a1550..37dc99cecdc 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); >> } >> >> @@ -76,8 +84,12 @@ int led_set_period(struct udevice *dev, int period_ms) >> { >> struct led_ops *ops = led_get_ops(dev); >> >> - if (!ops->set_period) >> - return -ENOSYS; >> + if (!ops->set_period) { >> + if (IS_ENABLED(CONFIG_LED_SW_BLINK)) >> + return led_sw_set_period(dev, period_ms); >> + else >> + return -ENOSYS; >> + } >> >> return ops->set_period(dev, period_ms); >> } >> diff --git a/drivers/led/led_sw_blink.c b/drivers/led/led_sw_blink.c >> new file mode 100644 >> index 00000000000..ab56111a60b >> --- /dev/null >> +++ b/drivers/led/led_sw_blink.c >> @@ -0,0 +1,106 @@ >> +// SPDX-License-Identifier: GPL-2.0+ >> +/* >> + * Software blinking helpers >> + * Copyright (C) 2024 IOPSYS Software Solutions AB >> + * Author: Mikhail Kshevetskiy <mikhail.kshevets...@iopsys.eu> >> + */ >> + >> +#include <dm.h> >> +#include <led.h> >> +#include <time.h> >> +#include <stdlib.h> >> + >> +static void led_sw_blink(struct cyclic_info *c) >> +{ >> + struct led_uc_plat *uc_plat; >> + struct udevice *dev; >> + struct led_ops *ops; >> + >> + uc_plat = container_of(c, struct led_uc_plat, cyclic); >> + dev = uc_plat->dev; >> + ops = led_get_ops(dev); >> + >> + switch (uc_plat->sw_blink_state) { >> + case LED_SW_BLINK_ST_OFF: >> + uc_plat->sw_blink_state = LED_SW_BLINK_ST_ON; >> + ops->set_state(dev, LEDST_ON); >> + break; >> + case LED_SW_BLINK_ST_ON: >> + uc_plat->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 cyclic_info *cyclic = &uc_plat->cyclic; >> + struct led_ops *ops = led_get_ops(dev); >> + int half_period_us; >> + char *name; >> + int len; >> + >> + half_period_us = period_ms * 1000 / 2; >> + >> + name = (char *)cyclic->name; >> + if (name == NULL) { >> + len = snprintf(NULL, 0, "led_sw_blink_%s", uc_plat->label); >> + if (len <= 0) >> + return -ENOMEM; >> + >> + name = malloc(len + 1); >> + if (!name) >> + return -ENOMEM; >> + >> + snprintf(name, len + 1, "led_sw_blink_%s", uc_plat->label); >> + } >> + >> + if (uc_plat->sw_blink_state == LED_SW_BLINK_ST_DISABLED) { >> + uc_plat->dev = dev; >> + cyclic_register(cyclic, led_sw_blink, half_period_us, name); >> + } else { >> + cyclic->delay_us = half_period_us; >> + cyclic->start_time_us = timer_get_us(); >> + } >> + >> + uc_plat->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); >> + >> + return (uc_plat->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); >> + >> + if (uc_plat->sw_blink_state != LED_SW_BLINK_ST_DISABLED) { >> + if (state == LEDST_BLINK) { >> + /* start blinking on next led_sw_blink() call */ >> + uc_plat->sw_blink_state = LED_SW_BLINK_ST_OFF; >> + return true; >> + } >> + >> + /* stop blinking */ >> + cyclic_unregister(&uc_plat->cyclic); >> + uc_plat->sw_blink_state = LED_SW_BLINK_ST_DISABLED; >> + } >> + >> + return false; >> +} >> diff --git a/include/led.h b/include/led.h >> index a6353166289..26955269d3e 100644 >> --- a/include/led.h >> +++ b/include/led.h >> @@ -20,6 +20,13 @@ 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_uc_plat - Platform data the uclass stores about each device >> * >> @@ -29,6 +36,11 @@ enum led_state_t { >> struct led_uc_plat { >> const char *label; >> enum led_state_t default_state; >> +#ifdef CONFIG_LED_SW_BLINK >> + struct udevice *dev; >> + struct cyclic_info cyclic; >> + enum led_sw_blink_state_t sw_blink_state; >> +#endif >> }; >> >> /** >> @@ -118,4 +130,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 >> -- >> 2.39.2 >> >>