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 <michael.polyntsov@iopsys.eu>
Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
Reviewed-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Michael Polyntsov
2024-07-19 13:12:12 +04:00
committed by Tom Rini
parent 2a15c676fa
commit b557f55e90
5 changed files with 166 additions and 1 deletions

View File

@@ -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

View File

@@ -4,6 +4,7 @@
# Written by Simon Glass <sjg@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

View File

@@ -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;
}

117
drivers/led/led_sw_blink.c Normal file
View File

@@ -0,0 +1,117 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Software blinking helpers
* Copyright (C) 2024 IOPSYS Software Solutions AB
* Author: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
*/
#include <dm.h>
#include <led.h>
#include <time.h>
#include <stdlib.h>
#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;
}