Merge patch series "led: introduce LED boot and activity function"

Christian Marangi <ansuelsmth@gmail.com> says:

This series is a reworked version of the previous seried:
misc: introduce STATUS LED activity function

This series port and expand the legacy concept of LED boot from
the legacy Status LED API to new LED API.

One thing that many device need is a way to communicate to the
user that the device is actually doing something.

This is especially useful for recovery steps where an
user (for example) insert an USB drive, keep a button pressed
and the device autorecover.

There is currently no way to signal the user externally that
the bootloader is processing/recoverying aside from setting
a LED on.

A solid LED on is not enough and won't actually signal any
kind of progress.
Solution is the good old blinking LED but uboot doesn't
suggest (and support) interrupts and almost all the LED
are usually GPIO LED that doesn't support HW blink.

Additional Kconfg are also introduced to set the LED boot and
activity. Those are referenced by label.

A documentation for old and these new LED API is created.
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index 8412506..9bf44ae 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -101,6 +101,11 @@
 			bootscr-ram-offset = /bits/ 64 <0x12345678>;
 			bootscr-flash-offset = /bits/ 64 <0>;
 			bootscr-flash-size = /bits/ 64 <0x2000>;
+			boot-led = "sandbox:green";
+			activity-led = "sandbox:red";
+			testing-bool;
+			testing-int = <123>;
+			testing-str = "testing";
 		};
 	};
 
diff --git a/cmd/mtd.c b/cmd/mtd.c
index 795aaa2..f178d7b 100644
--- a/cmd/mtd.c
+++ b/cmd/mtd.c
@@ -10,6 +10,7 @@
 
 #include <command.h>
 #include <console.h>
+#include <led.h>
 #if CONFIG_IS_ENABLED(CMD_MTD_OTP)
 #include <hexdump.h>
 #endif
@@ -558,6 +559,8 @@
 	while (mtd_block_isbad(mtd, off))
 		off += mtd->erasesize;
 
+	led_activity_blink();
+
 	/* Loop over the pages to do the actual read/write */
 	while (remaining) {
 		/* Skip the block if it is bad */
@@ -585,6 +588,8 @@
 		io_op.oobbuf += io_op.oobretlen;
 	}
 
+	led_activity_off();
+
 	if (!ret && dump)
 		mtd_dump_device_buf(mtd, start_off, buf, len, woob);
 
@@ -652,6 +657,8 @@
 	erase_op.addr = off;
 	erase_op.len = mtd->erasesize;
 
+	led_activity_blink();
+
 	while (len) {
 		if (!scrub) {
 			ret = mtd_block_isbad(mtd, erase_op.addr);
@@ -680,6 +687,8 @@
 		erase_op.addr += mtd->erasesize;
 	}
 
+	led_activity_off();
+
 	if (ret && ret != -EIO)
 		ret = CMD_RET_FAILURE;
 	else
diff --git a/cmd/ubi.c b/cmd/ubi.c
index 0e62e44..56d7da8 100644
--- a/cmd/ubi.c
+++ b/cmd/ubi.c
@@ -14,6 +14,7 @@
 #include <command.h>
 #include <env.h>
 #include <exports.h>
+#include <led.h>
 #include <malloc.h>
 #include <memalign.h>
 #include <mtd.h>
@@ -488,10 +489,18 @@
 
 int ubi_volume_write(char *volume, void *buf, loff_t offset, size_t size)
 {
+	int ret;
+
+	led_activity_blink();
+
 	if (!offset)
-		return ubi_volume_begin_write(volume, buf, size, size);
+		ret = ubi_volume_begin_write(volume, buf, size, size);
+	else
+		ret = ubi_volume_offset_write(volume, buf, offset, size);
 
-	return ubi_volume_offset_write(volume, buf, offset, size);
+	led_activity_off();
+
+	return ret;
 }
 
 int ubi_volume_read(char *volume, char *buf, loff_t offset, size_t size)
diff --git a/common/board_r.c b/common/board_r.c
index 4faaa20..1acad06 100644
--- a/common/board_r.c
+++ b/common/board_r.c
@@ -40,6 +40,7 @@
 #include <initcall.h>
 #include <kgdb.h>
 #include <irq_func.h>
+#include <led.h>
 #include <malloc.h>
 #include <mapmem.h>
 #include <miiphy.h>
@@ -460,17 +461,28 @@
 }
 #endif
 
-#if defined(CONFIG_LED_STATUS)
 static int initr_status_led(void)
 {
-#if defined(CONFIG_LED_STATUS_BOOT)
-	status_led_set(CONFIG_LED_STATUS_BOOT, CONFIG_LED_STATUS_BLINKING);
-#else
 	status_led_init();
-#endif
+
 	return 0;
 }
-#endif
+
+static int initr_boot_led_blink(void)
+{
+	status_led_boot_blink();
+
+	led_boot_blink();
+
+	return 0;
+}
+
+static int initr_boot_led_on(void)
+{
+	led_boot_on();
+
+	return 0;
+}
 
 #ifdef CONFIG_CMD_NET
 static int initr_net(void)
@@ -725,9 +737,8 @@
 #if defined(CONFIG_MICROBLAZE) || defined(CONFIG_M68K)
 	timer_init,		/* initialize timer */
 #endif
-#if defined(CONFIG_LED_STATUS)
 	initr_status_led,
-#endif
+	initr_boot_led_blink,
 	/* PPC has a udelay(20) here dating from 2002. Why? */
 #ifdef CONFIG_BOARD_LATE_INIT
 	board_late_init,
@@ -750,6 +761,7 @@
 #if defined(CFG_PRAM)
 	initr_mem,
 #endif
+	initr_boot_led_on,
 	run_main_loop,
 };
 
diff --git a/doc/api/index.rst b/doc/api/index.rst
index ec0b8ad..9f7f23f 100644
--- a/doc/api/index.rst
+++ b/doc/api/index.rst
@@ -14,6 +14,7 @@
    event
    getopt
    interrupt
+   led
    linker_lists
    lmb
    logging
diff --git a/doc/api/led.rst b/doc/api/led.rst
new file mode 100644
index 0000000..e52e350
--- /dev/null
+++ b/doc/api/led.rst
@@ -0,0 +1,10 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+LED
+===
+
+.. kernel-doc:: include/led.h
+   :doc: Overview
+
+.. kernel-doc:: include/led.h
+   :internal:
\ No newline at end of file
diff --git a/drivers/core/ofnode.c b/drivers/core/ofnode.c
index 7e3b371..677d51e 100644
--- a/drivers/core/ofnode.c
+++ b/drivers/core/ofnode.c
@@ -1735,6 +1735,39 @@
 	return ofnode_read_string(node, prop_name);
 }
 
+bool ofnode_options_read_bool(const char *prop_name)
+{
+	ofnode uboot;
+
+	uboot = ofnode_path("/options/u-boot");
+	if (!ofnode_valid(uboot))
+		return false;
+
+	return ofnode_read_bool(uboot, prop_name);
+}
+
+int ofnode_options_read_int(const char *prop_name, int default_val)
+{
+	ofnode uboot;
+
+	uboot = ofnode_path("/options/u-boot");
+	if (!ofnode_valid(uboot))
+		return default_val;
+
+	return ofnode_read_u32_default(uboot, prop_name, default_val);
+}
+
+const char *ofnode_options_read_str(const char *prop_name)
+{
+	ofnode uboot;
+
+	uboot = ofnode_path("/options/u-boot");
+	if (!ofnode_valid(uboot))
+		return NULL;
+
+	return ofnode_read_string(uboot, prop_name);
+}
+
 int ofnode_read_bootscript_address(u64 *bootscr_address, u64 *bootscr_offset)
 {
 	int ret;
diff --git a/drivers/led/Kconfig b/drivers/led/Kconfig
index bee74b2..c98cbf9 100644
--- a/drivers/led/Kconfig
+++ b/drivers/led/Kconfig
@@ -9,6 +9,30 @@
 	  can provide access to board-specific LEDs. Use of the device tree
 	  for configuration is encouraged.
 
+config LED_BOOT
+	bool "Enable LED boot support"
+	help
+	  Enable LED boot support.
+
+	  LED boot is a specific LED assigned to signal boot operation status.
+	  Defined in Device Tree /options/u-boot node. Refer here for the supported
+	  options [1].
+
+	  [1] dtschema/schemas/options/u-boot.yaml
+
+config LED_ACTIVITY
+	bool "Enable LED activity support"
+	help
+	  Enable LED activity support.
+
+	  LED activity is a specific LED assigned to signal activity operation
+	  like file trasnfer, flash write/erase...
+
+	  Defined in Device Tree /options/u-boot node. Refer here for the supported
+	  options [1].
+
+	  [1] dtschema/schemas/options/u-boot.yaml
+
 config LED_BCM6328
 	bool "LED Support for BCM6328"
 	depends on LED && ARCH_BMIPS
diff --git a/drivers/led/led-uclass.c b/drivers/led/led-uclass.c
index 199d68b..05e0990 100644
--- a/drivers/led/led-uclass.c
+++ b/drivers/led/led-uclass.c
@@ -94,6 +94,144 @@
 	return -ENOSYS;
 }
 
+#ifdef CONFIG_LED_BOOT
+static int led_boot_get(struct udevice **devp, int *period_ms)
+{
+	struct led_uc_priv *priv;
+	struct uclass *uc;
+	int ret;
+
+	ret = uclass_get(UCLASS_LED, &uc);
+	if (ret)
+		return ret;
+
+	priv = uclass_get_priv(uc);
+	if (!priv->boot_led_label)
+		return -ENOENT;
+
+	if (period_ms)
+		*period_ms = priv->boot_led_period;
+
+	return led_get_by_label(priv->boot_led_label, devp);
+}
+
+int led_boot_on(void)
+{
+	struct udevice *dev;
+	int ret;
+
+	ret = led_boot_get(&dev, NULL);
+	if (ret)
+		return ret;
+
+	return led_set_state(dev, LEDST_ON);
+}
+
+int led_boot_off(void)
+{
+	struct udevice *dev;
+	int ret;
+
+	ret = led_boot_get(&dev, NULL);
+	if (ret)
+		return ret;
+
+	return led_set_state(dev, LEDST_OFF);
+}
+
+#if defined(CONFIG_LED_BLINK) || defined(CONFIG_LED_SW_BLINK)
+int led_boot_blink(void)
+{
+	struct udevice *dev;
+	int period_ms, ret;
+
+	ret = led_boot_get(&dev, &period_ms);
+	if (ret)
+		return ret;
+
+	ret = led_set_period(dev, period_ms);
+	if (ret) {
+		if (ret != -ENOSYS)
+			return ret;
+
+		/* fallback to ON with no set_period and no SW_BLINK */
+		return led_set_state(dev, LEDST_ON);
+	}
+
+	return led_set_state(dev, LEDST_BLINK);
+}
+#endif
+#endif
+
+#ifdef CONFIG_LED_ACTIVITY
+static int led_activity_get(struct udevice **devp, int *period_ms)
+{
+	struct led_uc_priv *priv;
+	struct uclass *uc;
+	int ret;
+
+	ret = uclass_get(UCLASS_LED, &uc);
+	if (ret)
+		return ret;
+
+	priv = uclass_get_priv(uc);
+	if (!priv->activity_led_label)
+		return -ENOENT;
+
+	if (period_ms)
+		*period_ms = priv->activity_led_period;
+
+	return led_get_by_label(priv->activity_led_label, devp);
+}
+
+int led_activity_on(void)
+{
+	struct udevice *dev;
+	int ret;
+
+	ret = led_activity_get(&dev, NULL);
+	if (ret)
+		return ret;
+
+	return led_set_state(dev, LEDST_ON);
+}
+
+int led_activity_off(void)
+{
+	struct udevice *dev;
+	int ret;
+
+	ret = led_activity_get(&dev, NULL);
+	if (ret)
+		return ret;
+
+	return led_set_state(dev, LEDST_OFF);
+}
+
+#if defined(CONFIG_LED_BLINK) || defined(CONFIG_LED_SW_BLINK)
+int led_activity_blink(void)
+{
+	struct udevice *dev;
+	int period_ms, ret;
+
+	ret = led_activity_get(&dev, &period_ms);
+	if (ret)
+		return ret;
+
+	ret = led_set_period(dev, period_ms);
+	if (ret) {
+		if (ret != -ENOSYS)
+			return ret;
+
+		/* fallback to ON with no set_period and no SW_BLINK */
+		return led_set_state(dev, LEDST_ON);
+	}
+
+	return led_set_state(dev, LEDST_BLINK);
+}
+#endif
+#endif
+
 static int led_post_bind(struct udevice *dev)
 {
 	struct led_uc_plat *uc_plat = dev_get_uclass_plat(dev);
@@ -158,10 +296,34 @@
 	return ret;
 }
 
+#if defined(CONFIG_LED_BOOT) || defined(CONFIG_LED_ACTIVITY)
+static int led_init(struct uclass *uc)
+{
+	struct led_uc_priv *priv = uclass_get_priv(uc);
+
+#ifdef CONFIG_LED_BOOT
+	priv->boot_led_label = ofnode_options_read_str("boot-led");
+	priv->boot_led_period = ofnode_options_read_int("boot-led-period", 250);
+#endif
+
+#ifdef CONFIG_LED_ACTIVITY
+	priv->activity_led_label = ofnode_options_read_str("activity-led");
+	priv->activity_led_period = ofnode_options_read_int("activity-led-period",
+							    250);
+#endif
+
+	return 0;
+}
+#endif
+
 UCLASS_DRIVER(led) = {
 	.id		= UCLASS_LED,
 	.name		= "led",
 	.per_device_plat_auto	= sizeof(struct led_uc_plat),
 	.post_bind	= led_post_bind,
 	.post_probe	= led_post_probe,
+#if defined(CONFIG_LED_BOOT) || defined(CONFIG_LED_ACTIVITY)
+	.init		= led_init,
+	.priv_auto	= sizeof(struct led_uc_priv),
+#endif
 };
diff --git a/drivers/led/led_sw_blink.c b/drivers/led/led_sw_blink.c
index 9e36edb..06a43db 100644
--- a/drivers/led/led_sw_blink.c
+++ b/drivers/led/led_sw_blink.c
@@ -103,8 +103,21 @@
 		return false;
 
 	if (state == LEDST_BLINK) {
-		/* start blinking on next led_sw_blink() call */
-		sw_blink->state = LED_SW_BLINK_ST_OFF;
+		struct led_ops *ops = led_get_ops(dev);
+
+		/*
+		 * toggle LED initially and start blinking on next
+		 * led_sw_blink() call.
+		 */
+		switch (ops->get_state(dev)) {
+		case LEDST_ON:
+			ops->set_state(dev, LEDST_OFF);
+			sw_blink->state = LED_SW_BLINK_ST_OFF;
+		default:
+			ops->set_state(dev, LEDST_ON);
+			sw_blink->state = LED_SW_BLINK_ST_ON;
+		}
+
 		return true;
 	}
 
diff --git a/include/dm/ofnode.h b/include/dm/ofnode.h
index 5795115..0787758 100644
--- a/include/dm/ofnode.h
+++ b/include/dm/ofnode.h
@@ -1588,6 +1588,47 @@
 const char *ofnode_conf_read_str(const char *prop_name);
 
 /**
+ * ofnode_options_read_bool() - Read a boolean value from the U-Boot options
+ *
+ * This reads a property from the /options/u-boot/ node of the devicetree.
+ *
+ * This only works with the control FDT.
+ *
+ * See dtschema/schemas/options/u-boot.yaml in dt-schema project for bindings
+ *
+ * @prop_name:	property name to look up
+ * Return: true, if it exists, false if not
+ */
+bool ofnode_options_read_bool(const char *prop_name);
+
+/**
+ * ofnode_options_read_int() - Read an integer value from the U-Boot options
+ *
+ * This reads a property from the /options/u-boot/ node of the devicetree.
+ *
+ * See dtschema/schemas/options/u-boot.yaml in dt-schema project for bindings
+ *
+ * @prop_name: property name to look up
+ * @default_val: default value to return if the property is not found
+ * Return: integer value, if found, or @default_val if not
+ */
+int ofnode_options_read_int(const char *prop_name, int default_val);
+
+/**
+ * ofnode_options_read_str() - Read a string value from the U-Boot options
+ *
+ * This reads a property from the /options/u-boot/ node of the devicetree.
+ *
+ * This only works with the control FDT.
+ *
+ * See dtschema/schemas/options/u-boot.yaml in dt-schema project for bindings
+ *
+ * @prop_name: property name to look up
+ * Return: string value, if found, or NULL if not
+ */
+const char *ofnode_options_read_str(const char *prop_name);
+
+/**
  * ofnode_read_bootscript_address() - Read bootscr-address or bootscr-ram-offset
  *
  * @bootscr_address: pointer to 64bit address where bootscr-address property value
diff --git a/include/led.h b/include/led.h
index 99f93c5..64247cd 100644
--- a/include/led.h
+++ b/include/led.h
@@ -9,6 +9,47 @@
 
 #include <stdbool.h>
 #include <cyclic.h>
+#include <dm/ofnode.h>
+
+/**
+ * DOC: Overview
+ *
+ * Generic LED API provided when a supported compatible is defined in DeviceTree.
+ *
+ * To enable support for LEDs, enable the `CONFIG_LED` Kconfig option.
+ *
+ * The most common implementation is for GPIO-connected LEDs. If using GPIO-connected LEDs,
+ * enable the `LED_GPIO` Kconfig option.
+ *
+ * `LED_BLINK` support requires LED driver support and is therefore optional. If LED blink
+ * functionality is needed, enable the `LED_BLINK` Kconfig option. If LED driver doesn't
+ * support HW Blink, SW Blink can be used with the Cyclic framework by enabling the
+ * CONFIG_LED_SW_BLINK.
+ *
+ * Boot and Activity LEDs are also supported. These LEDs can signal various system operations
+ * during runtime, such as boot initialization, file transfers, and flash write/erase operations.
+ *
+ * To enable a Boot LED, enable `CONFIG_LED_BOOT` and define in `/options/u-boot` root node the
+ * property `boot-led`. This will enable the specified LED to blink and turn ON when
+ * the bootloader initializes correctly.
+ *
+ * To enable an Activity LED, enable `CONFIG_LED_ACTIVITY` and define in `/options/u-boot` root
+ * node the property `activity-led`.
+ * This will enable the specified LED to blink and turn ON during file transfers or flash
+ * write/erase operations.
+ *
+ * Both Boot and Activity LEDs provide a simple API to turn the LED ON or OFF:
+ * `led_boot_on()`, `led_boot_off()`, `led_activity_on()`, and `led_activity_off()`.
+ *
+ * Both configurations can optionally define a `boot/activity-led-period` property
+ * if `CONFIG_LED_BLINK` or `CONFIG_LED_SW_BLINK` is enabled for LED blink operations, which
+ * is usually used by the Activity LED. If not defined the default value of 250 (ms) is used.
+ *
+ * When `CONFIG_LED_BLINK` or `CONFIG_LED_SW_BLINK` is enabled, additional APIs are exposed:
+ * `led_boot_blink()` and `led_activity_blink()`. Note that if `CONFIG_LED_BLINK` or
+ * `CONFIG_LED_SW_BLINK` is disabled, these APIs will behave like the `led_boot_on()` and
+ * `led_activity_on()` APIs, respectively.
+ */
 
 struct udevice;
 
@@ -40,6 +81,7 @@
  *
  * @label:	LED label
  * @default_state:	LED default state
+ * @sw_blink:	LED software blink struct
  */
 struct led_uc_plat {
 	const char *label;
@@ -52,10 +94,22 @@
 /**
  * struct led_uc_priv - Private data the uclass stores about each device
  *
- * @period_ms:	Flash period in milliseconds
+ * @boot_led_label:	Boot LED label
+ * @activity_led_label:	Activity LED label
+ * @boot_led_dev:	Boot LED dev
+ * @activity_led_dev:	Activity LED dev
+ * @boot_led_period:	Boot LED blink period
+ * @activity_led_period: Activity LED blink period
  */
 struct led_uc_priv {
-	int period_ms;
+#ifdef CONFIG_LED_BOOT
+	const char *boot_led_label;
+	int boot_led_period;
+#endif
+#ifdef CONFIG_LED_ACTIVITY
+	const char *activity_led_label;
+	int activity_led_period;
+#endif
 };
 
 struct led_ops {
@@ -141,4 +195,93 @@
 bool led_sw_is_blinking(struct udevice *dev);
 bool led_sw_on_state_change(struct udevice *dev, enum led_state_t state);
 
+#ifdef CONFIG_LED_BOOT
+
+/**
+ * led_boot_on() - turn ON the designated LED for booting
+ *
+ * Return: 0 if OK, -ve on error
+ */
+int led_boot_on(void);
+
+/**
+ * led_boot_off() - turn OFF the designated LED for booting
+ *
+ * Return: 0 if OK, -ve on error
+ */
+int led_boot_off(void);
+
+#if defined(CONFIG_LED_BLINK) || defined(CONFIG_LED_SW_BLINK)
+/**
+ * led_boot_blink() - turn ON the designated LED for booting
+ *
+ * Return: 0 if OK, -ve on error
+ */
+int led_boot_blink(void);
+
+#else
+/* If LED BLINK is not supported/enabled, fallback to LED ON */
+#define led_boot_blink led_boot_on
+#endif
+#else
+static inline int led_boot_on(void)
+{
+	return -ENOSYS;
+}
+
+static inline int led_boot_off(void)
+{
+	return -ENOSYS;
+}
+
+static inline int led_boot_blink(void)
+{
+	return -ENOSYS;
+}
+#endif
+
+#ifdef CONFIG_LED_ACTIVITY
+
+/**
+ * led_activity_on() - turn ON the designated LED for activity
+ *
+ * Return: 0 if OK, -ve on error
+ */
+int led_activity_on(void);
+
+/**
+ * led_activity_off() - turn OFF the designated LED for activity
+ *
+ * Return: 0 if OK, -ve on error
+ */
+int led_activity_off(void);
+
+#if defined(CONFIG_LED_BLINK) || defined(CONFIG_LED_SW_BLINK)
+/**
+ * led_activity_blink() - turn ON the designated LED for activity
+ *
+ * Return: 0 if OK, -ve on error
+ */
+int led_activity_blink(void);
+#else
+/* If LED BLINK is not supported/enabled, fallback to LED ON */
+#define led_activity_blink led_activity_on
+#endif
+#else
+static inline int led_activity_on(void)
+{
+	return -ENOSYS;
+}
+
+static inline int led_activity_off(void)
+{
+	return -ENOSYS;
+}
+
+static inline int led_activity_blink(void)
+{
+	return -ENOSYS;
+}
+#endif
+
 #endif
diff --git a/include/status_led.h b/include/status_led.h
index 6707ab1..1282022 100644
--- a/include/status_led.h
+++ b/include/status_led.h
@@ -39,6 +39,13 @@
 void status_led_tick(unsigned long timestamp);
 void status_led_set(int led, int state);
 
+static inline void status_led_boot_blink(void)
+{
+#ifdef CONFIG_LED_STATUS_BOOT_ENABLE
+	status_led_set(CONFIG_LED_STATUS_BOOT, CONFIG_LED_STATUS_BLINKING);
+#endif
+}
+
 /*****  MVS v1  **********************************************************/
 #if (defined(CONFIG_MVS) && CONFIG_MVS < 2)
 # define STATUS_LED_PAR		im_ioport.iop_pdpar
@@ -72,6 +79,12 @@
 # include <asm/status_led.h>
 #endif
 
+#else
+
+static inline void status_led_init(void) { }
+static inline void status_led_set(int led, int state) { }
+static inline void status_led_boot_blink(void) { }
+
 #endif	/* CONFIG_LED_STATUS	*/
 
 /*
diff --git a/net/net.c b/net/net.c
index 1e0b7c8..bc16f0d 100644
--- a/net/net.c
+++ b/net/net.c
@@ -87,6 +87,7 @@
 #include <env_internal.h>
 #include <errno.h>
 #include <image.h>
+#include <led.h>
 #include <log.h>
 #include <net.h>
 #include <net6.h>
@@ -664,6 +665,9 @@
 			/* Invalidate the last protocol */
 			eth_set_last_protocol(BOOTP);
 
+			/* Turn off activity LED if triggered */
+			led_activity_off();
+
 			puts("\nAbort\n");
 			/* include a debug print as well incase the debug
 			   messages are directed to stderr */
diff --git a/net/tftp.c b/net/tftp.c
index d6744bc..704b20b 100644
--- a/net/tftp.c
+++ b/net/tftp.c
@@ -10,6 +10,7 @@
 #include <efi_loader.h>
 #include <env.h>
 #include <image.h>
+#include <led.h>
 #include <lmb.h>
 #include <log.h>
 #include <mapmem.h>
@@ -185,6 +186,7 @@
 #ifdef CONFIG_CMD_TFTPPUT
 	tftp_put_final_block_sent = 0;
 #endif
+	led_activity_blink();
 }
 
 #ifdef CONFIG_CMD_TFTPPUT
@@ -294,6 +296,9 @@
 			time_start * 1000, "/s");
 	}
 	puts("\ndone\n");
+
+	led_activity_off();
+
 	if (!tftp_put_active)
 		efi_set_bootdev("Net", "", tftp_filename,
 				map_sysmem(tftp_load_addr, 0),
diff --git a/test/dm/led.c b/test/dm/led.c
index e1509c3..884f641 100644
--- a/test/dm/led.c
+++ b/test/dm/led.c
@@ -137,3 +137,75 @@
 }
 DM_TEST(dm_test_led_blink, UTF_SCAN_PDATA | UTF_SCAN_FDT);
 #endif
+
+/* Test LED boot */
+#ifdef CONFIG_LED_BOOT
+static int dm_test_led_boot(struct unit_test_state *uts)
+{
+	struct udevice *dev
+
+	/* options/u-boot/boot-led is set to "sandbox:green" */
+	ut_assertok(led_get_by_label("sandbox:green", &dev));
+	ut_asserteq(LEDST_OFF, led_get_state(dev));
+	ut_assertok(led_boot_on());
+	ut_asserteq(LEDST_ON, led_get_state(dev));
+	ut_assertok(led_boot_off());
+	ut_asserteq(LEDST_OFF, led_get_state(dev));
+
+	return 0;
+}
+
+/* Test LED boot blink fallback */
+#ifndef CONFIG_LED_BLINK
+static int dm_test_led_boot(struct unit_test_state *uts)
+{
+	struct udevice *dev
+
+	/* options/u-boot/boot-led is set to "sandbox:green" */
+	ut_assertok(led_get_by_label("sandbox:green", &dev));
+	ut_asserteq(LEDST_OFF, led_get_state(dev));
+	ut_assertok(led_boot_blink());
+	ut_asserteq(LEDST_ON, led_get_state(dev));
+	ut_assertok(led_boot_off());
+	ut_asserteq(LEDST_OFF, led_get_state(dev));
+
+	return 0;
+}
+#endif
+#endif
+
+/* Test LED activity */
+#ifdef CONFIG_LED_ACTIVITY
+static int dm_test_led_boot(struct unit_test_state *uts)
+{
+	struct udevice *dev
+
+	/* options/u-boot/activity-led is set to "sandbox:red" */
+	ut_assertok(led_get_by_label("sandbox:red", &dev));
+	ut_asserteq(LEDST_OFF, led_get_state(dev));
+	ut_assertok(led_activity_on());
+	ut_asserteq(LEDST_ON, led_get_state(dev));
+	ut_assertok(led_activity_off());
+	ut_asserteq(LEDST_OFF, led_get_state(dev));
+
+	return 0;
+}
+
+/* Test LED activity blink fallback */
+#ifndef CONFIG_LED_BLINK
+static int dm_test_led_boot(struct unit_test_state *uts)
+{
+	struct udevice *dev
+
+	/* options/u-boot/activity-led is set to "sandbox:red" */
+	ut_assertok(led_get_by_label("sandbox:red", &dev));
+	ut_asserteq(LEDST_OFF, led_get_state(dev));
+	ut_assertok(led_activity_blink());
+	ut_asserteq(LEDST_ON, led_get_state(dev));
+	ut_assertok(led_activity_off());
+	ut_asserteq(LEDST_OFF, led_get_state(dev));
+
+	return 0;
+}
+#endif
+#endif
diff --git a/test/dm/ofnode.c b/test/dm/ofnode.c
index 859fc3a..ce99656 100644
--- a/test/dm/ofnode.c
+++ b/test/dm/ofnode.c
@@ -614,6 +614,15 @@
 	u64 bootscr_address, bootscr_offset;
 	u64 bootscr_flash_offset, bootscr_flash_size;
 
+	ut_assert(!ofnode_options_read_bool("missing"));
+	ut_assert(ofnode_options_read_bool("testing-bool"));
+
+	ut_asserteq(123, ofnode_options_read_int("testing-int", 0));
+	ut_asserteq(6, ofnode_options_read_int("missing", 6));
+
+	ut_assertnull(ofnode_options_read_str("missing"));
+	ut_asserteq_str("testing", ofnode_options_read_str("testing-str"));
+
 	ut_assertok(ofnode_read_bootscript_address(&bootscr_address,
 						   &bootscr_offset));
 	ut_asserteq_64(0, bootscr_address);