
From: Michael Polyntsov michael.polyntsov@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.
Signed-off-by: Michael Polyntsov michael.polyntsov@iopsys.eu Signed-off-by: Mikhail Kshevetskiy mikhail.kshevetskiy@iopsys.eu --- drivers/led/Kconfig | 9 ++ drivers/led/led-uclass.c | 190 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 195 insertions(+), 4 deletions(-)
diff --git a/drivers/led/Kconfig b/drivers/led/Kconfig index 9837960198d..4330f014239 100644 --- a/drivers/led/Kconfig +++ b/drivers/led/Kconfig @@ -73,6 +73,15 @@ 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. Does nothing if + driver supports blinking. + config SPL_LED bool "Enable LED support in SPL" depends on SPL_DM diff --git a/drivers/led/led-uclass.c b/drivers/led/led-uclass.c index a4be56fc258..b35964f2e99 100644 --- a/drivers/led/led-uclass.c +++ b/drivers/led/led-uclass.c @@ -15,6 +15,10 @@ #include <dm/root.h> #include <dm/uclass-internal.h>
+#ifdef CONFIG_LED_SW_BLINK +#include <malloc.h> +#endif + int led_bind_generic(struct udevice *parent, const char *driver_name) { struct udevice *dev; @@ -41,6 +45,7 @@ int led_get_by_label(const char *label, struct udevice **devp) ret = uclass_get(UCLASS_LED, &uc); if (ret) return ret; + uclass_foreach_dev(dev, uc) { struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev);
@@ -52,14 +57,180 @@ int led_get_by_label(const char *label, struct udevice **devp) return -ENODEV; }
-int led_set_state(struct udevice *dev, enum led_state_t state) +#ifdef CONFIG_LED_SW_BLINK + +enum led_sw_blink_state_t { + LED_SW_BLINK_ST_OFF = 0, + LED_SW_BLINK_ST_ON = 1, + LED_SW_BLINK_ST_NONE = 2, +}; + +struct sw_blink_state { + struct udevice *dev; + enum led_sw_blink_state_t cur_blink_state; +}; + +static bool led_driver_supports_hw_blinking(const struct udevice *dev) +{ + struct led_ops *ops = led_get_ops(dev); + + /* + * We assume that if driver supports set_period, then it correctly + * handles all other requests, for example, that + * led_set_state(LEDST_BLINK) works correctly. + */ + return ops->set_period != NULL; +} + +static const char *led_sw_label_to_cyclic_func_name(const char *label) +{ +#define MAX_NAME_LEN 50 + static char cyclic_func_name[MAX_NAME_LEN] = {0}; + + snprintf(cyclic_func_name, MAX_NAME_LEN, "sw_blink_%s", label); + return cyclic_func_name; +#undef MAX_NAME_LEN +} + +static struct cyclic_info *led_sw_find_blinking_led(const char *label) +{ + struct cyclic_info *cyclic; + const char *cyclic_name; + + cyclic_name = led_sw_label_to_cyclic_func_name(label); + + hlist_for_each_entry(cyclic, cyclic_get_list(), list) { + if (strcmp(cyclic->name, cyclic_name) == 0) + return cyclic; + } + + return NULL; +} + +static bool led_sw_is_blinking(struct udevice *dev) +{ + struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev); + struct cyclic_info *cyclic = led_sw_find_blinking_led(uc_plat->label); + + if (cyclic != NULL) { + struct sw_blink_state *state; + + state = (struct sw_blink_state *)cyclic->ctx; + return state->cur_blink_state != LED_SW_BLINK_ST_NONE; + } + + return false; +} + +static void led_sw_blink(void *void_state) +{ + struct sw_blink_state *state = (struct sw_blink_state *)void_state; + struct udevice *dev = state->dev; + struct led_ops *ops = led_get_ops(dev); + + switch (state->cur_blink_state) { + case LED_SW_BLINK_ST_OFF: + state->cur_blink_state = LED_SW_BLINK_ST_ON; + ops->set_state(dev, LEDST_ON); + break; + case LED_SW_BLINK_ST_ON: + state->cur_blink_state = LED_SW_BLINK_ST_OFF; + ops->set_state(dev, LEDST_OFF); + break; + case LED_SW_BLINK_ST_NONE: + /* + * led_set_period has been called, but + * led_set_state(LDST_BLINK) has not yet, + * so doing nothing + */ + break; + } +} + +static void led_sw_free_cyclic(struct cyclic_info *cyclic) +{ + free(cyclic->ctx); + cyclic_unregister(cyclic); +} + +static int led_sw_set_period(struct udevice *dev, int period_ms) +{ + struct cyclic_info *cyclic; + struct sw_blink_state *state; + struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev); + const char *cyclic_func_name; + + state = malloc(sizeof(struct sw_blink_state)); + if (state == NULL) { + printf("Allocating memory for sw_blink_state for %s failed\n", + uc_plat->label); + return -ENOMEM; + } + + state->cur_blink_state = LED_SW_BLINK_ST_NONE; + state->dev = dev; + + /* + * Make sure that there is no cyclic function already + * registered for this label + */ + cyclic = led_sw_find_blinking_led(uc_plat->label); + if (cyclic != NULL) + led_sw_free_cyclic(cyclic); + + cyclic_func_name = led_sw_label_to_cyclic_func_name(uc_plat->label); + + cyclic = cyclic_register(led_sw_blink, period_ms * 1000, + cyclic_func_name, (void *)state); + if (cyclic == NULL) { + printf("Registering of blinking function for %s failed\n", + uc_plat->label); + free(state); + return -ENOMEM; + } + + return 0; +} + +#endif + +int led_set_state(struct udevice *dev, enum led_state_t new_state) { struct led_ops *ops = led_get_ops(dev);
if (!ops->set_state) return -ENOSYS;
- return ops->set_state(dev, state); +#ifdef CONFIG_LED_SW_BLINK + if (!led_driver_supports_hw_blinking(dev)) { + struct cyclic_info *cyclic; + struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev); + + cyclic = led_sw_find_blinking_led(uc_plat->label); + + if (cyclic != NULL) { + if (new_state == LEDST_BLINK) { + struct sw_blink_state *cur_st; + + cur_st = (struct sw_blink_state *)cyclic->ctx; + + /* + * Next call to led_sw_blink will start blinking + */ + cur_st->cur_blink_state = LED_SW_BLINK_ST_OFF; + return 0; + } + + /* + * Changing current blinking state to + * something else + */ + led_sw_free_cyclic(cyclic); + } + } +#endif + + return ops->set_state(dev, new_state); }
enum led_state_t led_get_state(struct udevice *dev) @@ -69,19 +240,31 @@ enum led_state_t led_get_state(struct udevice *dev) if (!ops->get_state) return -ENOSYS;
+#ifdef CONFIG_LED_SW_BLINK + if (!led_driver_supports_hw_blinking(dev) && led_sw_is_blinking(dev)) + return LEDST_BLINK; +#endif + return ops->get_state(dev); }
#ifdef CONFIG_LED_BLINK + int led_set_period(struct udevice *dev, int period_ms) { struct led_ops *ops = led_get_ops(dev);
- if (!ops->set_period) + if (!ops->set_period) { +#ifdef CONFIG_LED_SW_BLINK + return led_sw_set_period(dev, period_ms); +#else return -ENOSYS; +#endif + }
return ops->set_period(dev, period_ms); } + #endif
static int led_post_bind(struct udevice *dev) @@ -113,7 +296,6 @@ static int led_post_bind(struct udevice *dev) * probe() to configure its default state during startup. */ dev_or_flags(dev, DM_FLAG_PROBE_AFTER_BIND); - return 0; }