Merge tag 'efi-next-2022-09-14' of https://source.denx.de/u-boot/custodians/u-boot-efi into next

Pull request for efi next

UEFI:

Implement a command eficonfig to maintain Load Options and boot order via
menus.
diff --git a/MAINTAINERS b/MAINTAINERS
index 1ebcd36..25a11a0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -795,6 +795,13 @@
 F:	arch/m68k/
 F:	doc/arch/m68k.rst
 
+CYCLIC
+M:	Stefan Roese <sr@denx.de>
+S:	Maintained
+F:	cmd/cyclic.c
+F:	common/cyclic.c
+F:	include/cyclic.h
+
 DFU
 M:	Lukasz Majewski <lukma@denx.de>
 S:	Maintained
diff --git a/board/Marvell/octeon_nic23/board.c b/board/Marvell/octeon_nic23/board.c
index 3e2c544..08b1aa4 100644
--- a/board/Marvell/octeon_nic23/board.c
+++ b/board/Marvell/octeon_nic23/board.c
@@ -3,8 +3,10 @@
  * Copyright (C) 2021-2022 Stefan Roese <sr@denx.de>
  */
 
+#include <cyclic.h>
 #include <dm.h>
 #include <ram.h>
+#include <time.h>
 #include <asm/gpio.h>
 
 #include <mach/octeon_ddr.h>
@@ -15,11 +17,90 @@
 #include <mach/cvmx-helper-cfg.h>
 #include <mach/cvmx-helper-util.h>
 #include <mach/cvmx-bgxx-defs.h>
+#include <mach/cvmx-dtx-defs.h>
 
 #include "board_ddr.h"
 
+/**
+ * cvmx_spem#_cfg_rd
+ *
+ * This register allows read access to the configuration in the PCIe core.
+ *
+ */
+union cvmx_spemx_cfg_rd {
+	u64 u64;
+	struct cvmx_spemx_cfg_rd_s {
+		u64 data                         : 32;
+		u64 addr                         : 32;
+	} s;
+	struct cvmx_spemx_cfg_rd_s            cn73xx;
+};
+
+/**
+ * cvmx_spem#_cfg_wr
+ *
+ * This register allows write access to the configuration in the PCIe core.
+ *
+ */
+union cvmx_spemx_cfg_wr {
+	u64 u64;
+	struct cvmx_spemx_cfg_wr_s {
+		u64 data                         : 32;
+		u64 addr                         : 32;
+	} s;
+	struct cvmx_spemx_cfg_wr_s            cn73xx;
+};
+
+/**
+ * cvmx_spem#_flr_pf_stopreq
+ *
+ * PF function level reset stop outbound requests register.
+ * Hardware automatically sets the STOPREQ bit for the PF when it enters a
+ * function level reset (FLR).  Software is responsible for clearing the STOPREQ
+ * bit but must not do so prior to hardware taking down the FLR, which could be
+ * as long as 100ms.  It may be appropriate for software to wait longer before clearing
+ * STOPREQ, software may need to drain deep DPI queues for example.
+ * Whenever SPEM receives a PF or child VF request mastered by CNXXXX over S2M (i.e. P or NP),
+ * when STOPREQ is set for the function, SPEM will discard the outgoing request
+ * before sending it to the PCIe core.  If a NP, SPEM will schedule an immediate
+ * SWI_RSP_ERROR completion for the request - no timeout is required.
+ * In both cases, SPEM()_DBG_PF()_INFO[P()_BMD_E] will be set and a error
+ * interrupt is generated.
+ *
+ * STOPREQ mimics the behavior of PCIEEP()_CFG001[ME] for outbound requests that will
+ * master the PCIe bus (P and NP).
+ *
+ * STOPREQ will have no effect on completions returned by CNXXXX over the S2M,
+ * nor on M2S traffic.
+ *
+ * When a PF()_STOPREQ is set, none of the associated
+ * PEM()_FLR_PF()_VF_STOPREQ[VF_STOPREQ] will be set.
+ *
+ * STOPREQ is reset when the MAC is reset, and is not reset after a chip soft reset.
+ */
+union cvmx_spemx_flr_pf_stopreq {
+	u64 u64;
+	struct cvmx_spemx_flr_pf_stopreq_s {
+		u64 reserved_3_63                : 61;
+		u64 pf2_stopreq                  : 1;
+		u64 pf1_stopreq                  : 1;
+		u64 pf0_stopreq                  : 1;
+	} s;
+	struct cvmx_spemx_flr_pf_stopreq_s    cn73xx;
+};
+
+#define CVMX_SPEMX_CFG_WR(offset)		0x00011800C0000028ull
+#define CVMX_SPEMX_CFG_RD(offset)		0x00011800C0000030ull
+#define CVMX_SPEMX_FLR_PF_STOPREQ(offset)	0x00011800C0000218ull
+
+#define DTX_SELECT_LTSSM		0x0
+#define DTX_SELECT_LTSSM_ENA		0x3ff
+#define LTSSM_L0			0x11
+
 #define NIC23_DEF_DRAM_FREQ		800
 
+static u32 pci_cfgspace_reg0[2] = { 0, 0 };
+
 static u8 octeon_nic23_cfg0_spd_values[512] = {
 	OCTEON_NIC23_CFG0_SPD_VALUES
 };
@@ -145,8 +226,118 @@
 	      cvmx_qlm_measure_clock(4), cvmx_qlm_measure_clock(5));
 }
 
+/**
+ * If there is a PF FLR then the PCI EEPROM is not re-read.  In this case
+ * we need to re-program the vendor and device ID immediately after hardware
+ * completes FLR.
+ *
+ * PCI spec requires FLR to be completed within 100ms.  The user who triggered
+ * FLR expects hardware to finish FLR within 100ms, otherwise the user will
+ * end up reading DEVICE_ID incorrectly from the reset value.
+ * CN23XX exits FLR at any point between 66 and 99ms, so U-Boot has to wait
+ * 99ms to let hardware finish its part, then finish reprogramming the
+ * correct device ID before the end of 100ms.
+ *
+ * Note: this solution only works properly when there is no other activity
+ * within U-Boot for 100ms from the time FLR is triggered.
+ *
+ * This function gets called every 100usec.  If FLR happens during any
+ * other activity like bootloader/image update then it is possible that
+ * this function does not get called for more than the FLR period which will
+ * cause the PF device ID restore to happen after whoever initiated the FLR to
+ * read the incorrect device ID 0x9700 (reset value) instead of 0x9702
+ * (restored value).
+ */
+static void octeon_board_restore_pf(void *ctx)
+{
+	union cvmx_spemx_flr_pf_stopreq stopreq;
+	static bool start_initialized[2] = {false, false};
+	bool pf0_flag, pf1_flag;
+	u64 ltssm_bits;
+	const u64 pf_flr_wait_usecs = 99700;
+	u64 elapsed_usecs;
+	union cvmx_spemx_cfg_wr cfg_wr;
+	union cvmx_spemx_cfg_rd cfg_rd;
+	static u64 start_us[2];
+	int pf_num;
+
+	csr_wr(CVMX_DTX_SPEM_SELX(0), DTX_SELECT_LTSSM);
+	csr_rd(CVMX_DTX_SPEM_SELX(0));
+	csr_wr(CVMX_DTX_SPEM_ENAX(0), DTX_SELECT_LTSSM_ENA);
+	csr_rd(CVMX_DTX_SPEM_ENAX(0));
+	ltssm_bits = csr_rd(CVMX_DTX_SPEM_DATX(0));
+	if (((ltssm_bits >> 3) & 0x3f) != LTSSM_L0)
+		return;
+
+	stopreq.u64 = csr_rd(CVMX_SPEMX_FLR_PF_STOPREQ(0));
+	pf0_flag = stopreq.s.pf0_stopreq;
+	pf1_flag = stopreq.s.pf1_stopreq;
+	/* See if PF interrupt happened */
+	if (!(pf0_flag || pf1_flag))
+		return;
+
+	if (pf0_flag && !start_initialized[0]) {
+		start_initialized[0] = true;
+		start_us[0] = get_timer_us(0);
+	}
+
+	/* Store programmed PCIe DevID SPEM0 PF0 */
+	if (pf0_flag && !pci_cfgspace_reg0[0]) {
+		cfg_rd.s.addr = (0 << 24) | 0x0;
+		csr_wr(CVMX_SPEMX_CFG_RD(0), cfg_rd.u64);
+		cfg_rd.u64 = csr_rd(CVMX_SPEMX_CFG_RD(0));
+		pci_cfgspace_reg0[0] = cfg_rd.s.data;
+	}
+
+	if (pf1_flag && !start_initialized[1]) {
+		start_initialized[1] = true;
+		start_us[1] = get_timer_us(0);
+	}
+
+	/* Store programmed PCIe DevID SPEM0 PF1 */
+	if (pf1_flag && !pci_cfgspace_reg0[1]) {
+		cfg_rd.s.addr = (1 << 24) | 0x0;
+		csr_wr(CVMX_SPEMX_CFG_RD(0), cfg_rd.u64);
+		cfg_rd.u64 = csr_rd(CVMX_SPEMX_CFG_RD(0));
+		pci_cfgspace_reg0[1] = cfg_rd.s.data;
+	}
+
+	/* For PF, rewrite pci config space reg 0 */
+	for (pf_num = 0; pf_num < 2; pf_num++) {
+		if (!start_initialized[pf_num])
+			continue;
+
+		elapsed_usecs = get_timer_us(0) - start_us[pf_num];
+
+		if (elapsed_usecs > pf_flr_wait_usecs) {
+			/* Here, our measured FLR duration has passed;
+			 * check if device ID has been reset,
+			 * which indicates FLR completion (per MA team).
+			 */
+			cfg_rd.s.addr = (pf_num << 24) | 0x0;
+			csr_wr(CVMX_SPEMX_CFG_RD(0), cfg_rd.u64);
+			cfg_rd.u64 = csr_rd(CVMX_SPEMX_CFG_RD(0));
+			/* if DevID has NOT been reset, FLR is not yet
+			 * complete
+			 */
+			if (cfg_rd.s.data != pci_cfgspace_reg0[pf_num]) {
+				stopreq.s.pf0_stopreq = (pf_num == 0) ? 1 : 0;
+				stopreq.s.pf1_stopreq = (pf_num == 1) ? 1 : 0;
+				csr_wr(CVMX_SPEMX_FLR_PF_STOPREQ(0), stopreq.u64);
+
+				cfg_wr.u64 = 0;
+				cfg_wr.s.addr = (pf_num << 24) | 0;
+				cfg_wr.s.data = pci_cfgspace_reg0[pf_num];
+				csr_wr(CVMX_SPEMX_CFG_WR(0), cfg_wr.u64);
+				start_initialized[pf_num] = false;
+			}
+		}
+	}
+}
+
 int board_late_init(void)
 {
+	struct cyclic_info *cyclic;
 	struct gpio_desc gpio = {};
 	ofnode node;
 
@@ -164,6 +355,12 @@
 
 	board_configure_qlms();
 
+	/* Register cyclic function for PCIe FLR fixup */
+	cyclic = cyclic_register(octeon_board_restore_pf, 100,
+				 "pcie_flr_fix", NULL);
+	if (!cyclic)
+		printf("Registering of cyclic function failed\n");
+
 	return 0;
 }
 
diff --git a/boot/image-board.c b/boot/image-board.c
index 4e4d1c1..1be0a35 100644
--- a/boot/image-board.c
+++ b/boot/image-board.c
@@ -16,6 +16,7 @@
 #include <fpga.h>
 #include <image.h>
 #include <init.h>
+#include <log.h>
 #include <mapmem.h>
 #include <rtc.h>
 #include <watchdog.h>
@@ -24,7 +25,6 @@
 
 DECLARE_GLOBAL_DATA_PTR;
 
-#if CONFIG_IS_ENABLED(LEGACY_IMAGE_FORMAT)
 /**
  * image_get_ramdisk - get and verify ramdisk image
  * @rd_addr: ramdisk image start address
@@ -83,7 +83,6 @@
 
 	return rd_hdr;
 }
-#endif
 
 /*****************************************************************************/
 /* Shared dual-format routines */
@@ -174,29 +173,29 @@
 	if (to == from)
 		return;
 
-#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)
-	if (to > from) {
-		from += len;
-		to += len;
-	}
-	while (len > 0) {
-		size_t tail = (len > chunksz) ? chunksz : len;
-
-		WATCHDOG_RESET();
+	if (IS_ENABLED(CONFIG_HW_WATCHDOG) || IS_ENABLED(CONFIG_WATCHDOG)) {
 		if (to > from) {
-			to -= tail;
-			from -= tail;
+			from += len;
+			to += len;
 		}
-		memmove(to, from, tail);
-		if (to < from) {
-			to += tail;
-			from += tail;
+		while (len > 0) {
+			size_t tail = (len > chunksz) ? chunksz : len;
+
+			WATCHDOG_RESET();
+			if (to > from) {
+				to -= tail;
+				from -= tail;
+			}
+			memmove(to, from, tail);
+			if (to < from) {
+				to += tail;
+				from += tail;
+			}
+			len -= tail;
 		}
-		len -= tail;
+	} else {
+		memmove(to, from, len);
 	}
-#else	/* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */
-	memmove(to, from, len);
-#endif	/* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */
 }
 
 /**
@@ -314,7 +313,7 @@
  * select_ramdisk() - Select and locate the ramdisk to use
  *
  * @images: pointer to the bootm images structure
- * @select: name of ramdisk to select, or NULL for any
+ * @select: name of ramdisk to select, or hex address, NULL for any
  * @arch: expected ramdisk architecture
  * @rd_datap: pointer to a ulong variable, will hold ramdisk pointer
  * @rd_lenp: pointer to a ulong variable, will hold ramdisk length
@@ -324,13 +323,17 @@
 static int select_ramdisk(bootm_headers_t *images, const char *select, u8 arch,
 			  ulong *rd_datap, ulong *rd_lenp)
 {
+	const char *fit_uname_config;
+	const char *fit_uname_ramdisk;
+	bool done_select = !select;
+	bool done = false;
+	int rd_noffset;
 	ulong rd_addr;
 	char *buf;
 
-#if CONFIG_IS_ENABLED(FIT)
-		const char *fit_uname_config = images->fit_uname_cfg;
-		const char *fit_uname_ramdisk = NULL;
-		int rd_noffset;
+	if (CONFIG_IS_ENABLED(FIT)) {
+		fit_uname_config = images->fit_uname_cfg;
+		fit_uname_ramdisk = NULL;
 
 		if (select) {
 			ulong default_addr;
@@ -345,48 +348,47 @@
 			else
 				default_addr = image_load_addr;
 
-			if (fit_parse_conf(select, default_addr,
-					   &rd_addr, &fit_uname_config)) {
+			if (fit_parse_conf(select, default_addr, &rd_addr,
+					   &fit_uname_config)) {
 				debug("*  ramdisk: config '%s' from image at 0x%08lx\n",
 				      fit_uname_config, rd_addr);
+				done_select = true;
 			} else if (fit_parse_subimage(select, default_addr,
 						      &rd_addr,
 						      &fit_uname_ramdisk)) {
 				debug("*  ramdisk: subimage '%s' from image at 0x%08lx\n",
 				      fit_uname_ramdisk, rd_addr);
-			} else
-#endif
-			{
-				rd_addr = hextoul(select, NULL);
-				debug("*  ramdisk: cmdline image address = 0x%08lx\n",
-				      rd_addr);
+				done_select = true;
 			}
-#if CONFIG_IS_ENABLED(FIT)
-		} else {
-			/* use FIT configuration provided in first bootm
-			 * command argument. If the property is not defined,
-			 * quit silently (with -ENOPKG)
-			 */
-			rd_addr = map_to_sysmem(images->fit_hdr_os);
-			rd_noffset = fit_get_node_from_config(images,
-							      FIT_RAMDISK_PROP,
-							      rd_addr);
-			if (rd_noffset == -ENOENT)
-				return -ENOPKG;
-			else if (rd_noffset < 0)
-				return rd_noffset;
 		}
-#endif
-
-		/*
-		 * Check if there is an initrd image at the
-		 * address provided in the second bootm argument
-		 * check image type, for FIT images get FIT node.
+	}
+	if (!done_select) {
+		rd_addr = hextoul(select, NULL);
+		debug("*  ramdisk: cmdline image address = 0x%08lx\n", rd_addr);
+	}
+	if (CONFIG_IS_ENABLED(FIT) && !select) {
+		/* use FIT configuration provided in first bootm
+		 * command argument. If the property is not defined,
+		 * quit silently (with -ENOPKG)
 		 */
-		buf = map_sysmem(rd_addr, 0);
-		switch (genimg_get_format(buf)) {
-#if CONFIG_IS_ENABLED(LEGACY_IMAGE_FORMAT)
-		case IMAGE_FORMAT_LEGACY: {
+		rd_addr = map_to_sysmem(images->fit_hdr_os);
+		rd_noffset = fit_get_node_from_config(images, FIT_RAMDISK_PROP,
+						      rd_addr);
+		if (rd_noffset == -ENOENT)
+			return -ENOPKG;
+		else if (rd_noffset < 0)
+			return rd_noffset;
+	}
+
+	/*
+	 * Check if there is an initrd image at the
+	 * address provided in the second bootm argument
+	 * check image type, for FIT images get FIT node.
+	 */
+	buf = map_sysmem(rd_addr, 0);
+	switch (genimg_get_format(buf)) {
+	case IMAGE_FORMAT_LEGACY:
+		if (CONFIG_IS_ENABLED(LEGACY_IMAGE_FORMAT)) {
 			const image_header_t *rd_hdr;
 
 			printf("## Loading init Ramdisk from Legacy Image at %08lx ...\n",
@@ -401,15 +403,15 @@
 
 			*rd_datap = image_get_data(rd_hdr);
 			*rd_lenp = image_get_data_size(rd_hdr);
-			break;
+			done = true;
 		}
-#endif
-#if CONFIG_IS_ENABLED(FIT)
-		case IMAGE_FORMAT_FIT:
-			rd_noffset = fit_image_load(images,
-						    rd_addr, &fit_uname_ramdisk,
-						    &fit_uname_config, arch,
-						    IH_TYPE_RAMDISK,
+		break;
+	case IMAGE_FORMAT_FIT:
+		if (CONFIG_IS_ENABLED(FIT)) {
+			rd_noffset = fit_image_load(images, rd_addr,
+						    &fit_uname_ramdisk,
+						    &fit_uname_config,
+						    arch, IH_TYPE_RAMDISK,
 						    BOOTSTAGE_ID_FIT_RD_START,
 						    FIT_LOAD_OPTIONAL_NON_ZERO,
 						    rd_datap, rd_lenp);
@@ -419,29 +421,41 @@
 			images->fit_hdr_rd = map_sysmem(rd_addr, 0);
 			images->fit_uname_rd = fit_uname_ramdisk;
 			images->fit_noffset_rd = rd_noffset;
-			break;
-#endif
-#ifdef CONFIG_ANDROID_BOOT_IMAGE
-		case IMAGE_FORMAT_ANDROID:
-			android_image_get_ramdisk((void *)images->os.start,
-						  rd_datap, rd_lenp);
-			break;
-#endif
-		default:
-			if (IS_ENABLED(CONFIG_SUPPORT_RAW_INITRD)) {
-				char *end = NULL;
+			done = true;
+		}
+		break;
+	case IMAGE_FORMAT_ANDROID:
+		if (IS_ENABLED(CONFIG_ANDROID_BOOT_IMAGE)) {
+			void *ptr = map_sysmem(images->os.start, 0);
+			int ret;
+
+			ret = android_image_get_ramdisk(ptr, rd_datap, rd_lenp);
+			unmap_sysmem(ptr);
+			if (ret)
+				return ret;
+			done = true;
+		}
+		break;
+	}
+
+	if (!done) {
+		if (IS_ENABLED(CONFIG_SUPPORT_RAW_INITRD)) {
+			char *end = NULL;
 
-				if (select)
-					end = strchr(select, ':');
-				if (end) {
-					*rd_lenp = hextoul(++end, NULL);
-					*rd_datap = rd_addr;
-					break;
-				}
+			if (select)
+				end = strchr(select, ':');
+			if (end) {
+				*rd_lenp = hextoul(++end, NULL);
+				*rd_datap = rd_addr;
+				done = true;
 			}
+		}
+
+		if (!done) {
 			puts("Wrong Ramdisk Image Format\n");
 			return -EINVAL;
 		}
+	}
 
 	return 0;
 }
@@ -538,7 +552,6 @@
 	return 0;
 }
 
-#if defined(CONFIG_LMB)
 /**
  * boot_ramdisk_high - relocate init ramdisk
  * @lmb: pointer to lmb handle, will be used for memory mgmt
@@ -632,7 +645,6 @@
 error:
 	return -1;
 }
-#endif
 
 int boot_get_setup(bootm_headers_t *images, u8 arch,
 		   ulong *setup_start, ulong *setup_len)
@@ -826,15 +838,13 @@
 	return 0;
 }
 
-#if defined(CONFIG_LMB)
-#ifdef CONFIG_SYS_BOOT_GET_CMDLINE
 /**
  * boot_get_cmdline - allocate and initialize kernel cmdline
  * @lmb: pointer to lmb handle, will be used for memory mgmt
  * @cmd_start: pointer to a ulong variable, will hold cmdline start
  * @cmd_end: pointer to a ulong variable, will hold cmdline end
  *
- * boot_get_cmdline() allocates space for kernel command line below
+ * This allocates space for kernel command line below
  * BOOTMAPSZ + env_get_bootm_low() address. If "bootargs" U-Boot environment
  * variable is present its contents is copied to allocated kernel
  * command line.
@@ -845,10 +855,19 @@
  */
 int boot_get_cmdline(struct lmb *lmb, ulong *cmd_start, ulong *cmd_end)
 {
+	int barg;
 	char *cmdline;
 	char *s;
 
-	cmdline = (char *)(ulong)lmb_alloc_base(lmb, CONFIG_SYS_BARGSIZE, 0xf,
+	/*
+	 * Help the compiler detect that this function is only called when
+	 * CONFIG_SYS_BOOT_GET_CMDLINE is enabled
+	 */
+	if (!IS_ENABLED(CONFIG_SYS_BOOT_GET_CMDLINE))
+		return 0;
+
+	barg = IF_ENABLED_INT(CONFIG_SYS_BOOT_GET_CMDLINE, CONFIG_SYS_BARGSIZE);
+	cmdline = (char *)(ulong)lmb_alloc_base(lmb, barg, 0xf,
 				env_get_bootm_mapsize() + env_get_bootm_low());
 	if (!cmdline)
 		return -1;
@@ -894,22 +913,22 @@
 
 	debug("## kernel board info at 0x%08lx\n", (ulong)*kbd);
 
-#if defined(DEBUG)
-	if (IS_ENABLED(CONFIG_CMD_BDI))
+	if (_DEBUG && IS_ENABLED(CONFIG_CMD_BDI))
 		do_bdinfo(NULL, 0, 0, NULL);
-#endif
 
 	return 0;
 }
-#endif
 
 int image_setup_linux(bootm_headers_t *images)
 {
 	ulong of_size = images->ft_len;
 	char **of_flat_tree = &images->ft_addr;
-	struct lmb *lmb = &images->lmb;
+	struct lmb *lmb = images_lmb(images);
 	int ret;
 
+	/* This function cannot be called without lmb support */
+	if (!CONFIG_IS_ENABLED(LMB))
+		return -EFAULT;
 	if (CONFIG_IS_ENABLED(OF_LIBFDT))
 		boot_fdt_add_mem_rsv_regions(lmb, *of_flat_tree);
 
@@ -936,7 +955,6 @@
 
 	return 0;
 }
-#endif
 
 void genimg_print_size(uint32_t size)
 {
diff --git a/cmd/Kconfig b/cmd/Kconfig
index ea506a0..37bc64d 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -2518,6 +2518,22 @@
 	  memory by coreboot before jumping to U-Boot. It can be useful for
 	  debugging the beaaviour of coreboot or U-Boot.
 
+config CMD_CYCLIC
+	bool "cyclic - Show information about cyclic functions"
+	depends on CYCLIC
+	default y
+	help
+	  This enables the 'cyclic' command which provides information about
+	  cyclic execution functions. This infrastructure allows registering
+	  functions to be executed cyclically, e.g. every 100ms. These commands
+	  are supported:
+
+	    cyclic list - list cyclic functions
+	    cyclic cyclic demo <cycletime_ms> <delay_us> - register cyclic
+		demo function
+
+	  See doc/develop/cyclic.rst for more details.
+
 config CMD_DIAG
 	bool "diag - Board diagnostics"
 	help
diff --git a/cmd/Makefile b/cmd/Makefile
index 3b027d3..0ef4e2e 100644
--- a/cmd/Makefile
+++ b/cmd/Makefile
@@ -56,6 +56,7 @@
 endif
 obj-$(CONFIG_CMD_ADTIMG) += adtimg.o
 obj-$(CONFIG_CMD_ABOOTIMG) += abootimg.o
+obj-$(CONFIG_CMD_CYCLIC) += cyclic.o
 obj-$(CONFIG_CMD_EVENT) += event.o
 obj-$(CONFIG_CMD_EXTENSION) += extension_board.o
 obj-$(CONFIG_CMD_ECHO) += echo.o
diff --git a/cmd/cyclic.c b/cmd/cyclic.c
new file mode 100644
index 0000000..c1bc556
--- /dev/null
+++ b/cmd/cyclic.c
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * A general-purpose cyclic execution infrastructure, to allow "small"
+ * (run-time wise) functions to be executed at a specified frequency.
+ * Things like LED blinking or watchdog triggering are examples for such
+ * tasks.
+ *
+ * Copyright (C) 2022 Stefan Roese <sr@denx.de>
+ */
+
+#include <common.h>
+#include <command.h>
+#include <cyclic.h>
+#include <div64.h>
+#include <malloc.h>
+#include <linux/delay.h>
+
+struct cyclic_demo_info {
+	uint delay_us;
+};
+
+static void cyclic_demo(void *ctx)
+{
+	struct cyclic_demo_info *info = ctx;
+
+	/* Just a small dummy delay here */
+	udelay(info->delay_us);
+}
+
+static int do_cyclic_demo(struct cmd_tbl *cmdtp, int flag, int argc,
+			  char *const argv[])
+{
+	struct cyclic_demo_info *info;
+	struct cyclic_info *cyclic;
+	uint time_ms;
+
+	if (argc < 3)
+		return CMD_RET_USAGE;
+
+	info = malloc(sizeof(struct cyclic_demo_info));
+	if (!info) {
+		printf("out of memory\n");
+                return CMD_RET_FAILURE;
+	}
+
+	time_ms = simple_strtoul(argv[1], NULL, 0);
+	info->delay_us = simple_strtoul(argv[2], NULL, 0);
+
+	/* Register demo cyclic function */
+	cyclic = cyclic_register(cyclic_demo, time_ms * 1000, "cyclic_demo",
+				 info);
+	if (!cyclic)
+		printf("Registering of cyclic_demo failed\n");
+
+	printf("Registered function \"%s\" to be executed all %dms\n",
+	       "cyclic_demo", time_ms);
+
+	return 0;
+}
+
+static int do_cyclic_list(struct cmd_tbl *cmdtp, int flag, int argc,
+			  char *const argv[])
+{
+	struct cyclic_info *cyclic, *tmp;
+	u64 cnt, freq;
+
+	list_for_each_entry_safe(cyclic, tmp, cyclic_get_list(), list) {
+		cnt = cyclic->run_cnt * 1000000ULL * 100ULL;
+		freq = lldiv(cnt, timer_get_us() - cyclic->start_time_us);
+		printf("function: %s, cpu-time: %lld us, frequency: %lld.%02d times/s\n",
+		       cyclic->name, cyclic->cpu_time_us,
+		       lldiv(freq, 100), do_div(freq, 100));
+	}
+
+	return 0;
+}
+
+static char cyclic_help_text[] =
+	"cyclic demo <cycletime_ms> <delay_us> - register cyclic demo function\n"
+	"cyclic list - list cyclic functions\n";
+
+U_BOOT_CMD_WITH_SUBCMDS(cyclic, "Cyclic", cyclic_help_text,
+	U_BOOT_SUBCMD_MKENT(demo, 3, 1, do_cyclic_demo),
+	U_BOOT_SUBCMD_MKENT(list, 1, 1, do_cyclic_list));
diff --git a/common/Kconfig b/common/Kconfig
index 2c3f7f4..3e44acd 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -545,6 +545,26 @@
 
 menu "Start-up hooks"
 
+config CYCLIC
+	bool "General-purpose cyclic execution mechanism"
+	help
+	  This enables a general-purpose cyclic execution infrastructure,
+	  to allow "small" (run-time wise) functions to be executed at
+	  a specified frequency. Things like LED blinking or watchdog
+	  triggering are examples for such tasks.
+
+if CYCLIC
+
+config CYCLIC_MAX_CPU_TIME_US
+	int "Sets the max allowed time for a cyclic function in us"
+	default 1000
+	help
+	  The max allowed time for a cyclic function in us. If a functions
+	  takes longer than this duration this function will get unregistered
+	  automatically.
+
+endif # CYCLIC
+
 config EVENT
 	bool "General-purpose event-handling mechanism"
 	default y if SANDBOX
diff --git a/common/Makefile b/common/Makefile
index 2ed8672..1d56c9f 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -84,6 +84,7 @@
 endif
 endif
 
+obj-$(CONFIG_CYCLIC) += cyclic.o
 obj-$(CONFIG_$(SPL_TPL_)EVENT) += event.o
 
 obj-$(CONFIG_$(SPL_TPL_)HASH) += hash.o
diff --git a/common/board_f.c b/common/board_f.c
index 18e2246..deb46be 100644
--- a/common/board_f.c
+++ b/common/board_f.c
@@ -16,6 +16,7 @@
 #include <console.h>
 #include <cpu.h>
 #include <cpu_func.h>
+#include <cyclic.h>
 #include <display_options.h>
 #include <dm.h>
 #include <env.h>
@@ -828,6 +829,7 @@
 	initf_malloc,
 	log_init,
 	initf_bootstage,	/* uses its own timer, so does not need DM */
+	cyclic_init,
 	event_init,
 #ifdef CONFIG_BLOBLIST
 	bloblist_init,
diff --git a/common/board_r.c b/common/board_r.c
index 56eb60f..062bc3e 100644
--- a/common/board_r.c
+++ b/common/board_r.c
@@ -13,6 +13,7 @@
 #include <api.h>
 #include <bootstage.h>
 #include <cpu_func.h>
+#include <cyclic.h>
 #include <display_options.h>
 #include <exports.h>
 #ifdef CONFIG_MTD_NOR_FLASH
@@ -611,6 +612,7 @@
 #endif
 	initr_barrier,
 	initr_malloc,
+	cyclic_init,
 	log_init,
 	initr_bootstage,	/* Needs malloc() but has its own timer */
 #if defined(CONFIG_CONSOLE_RECORD)
diff --git a/common/cyclic.c b/common/cyclic.c
new file mode 100644
index 0000000..cd5dcb1
--- /dev/null
+++ b/common/cyclic.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * A general-purpose cyclic execution infrastructure, to allow "small"
+ * (run-time wise) functions to be executed at a specified frequency.
+ * Things like LED blinking or watchdog triggering are examples for such
+ * tasks.
+ *
+ * Copyright (C) 2022 Stefan Roese <sr@denx.de>
+ */
+
+#include <cyclic.h>
+#include <log.h>
+#include <malloc.h>
+#include <time.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <asm/global_data.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct list_head *cyclic_get_list(void)
+{
+	return &gd->cyclic->cyclic_list;
+}
+
+struct cyclic_info *cyclic_register(cyclic_func_t func, uint64_t delay_us,
+				    const char *name, void *ctx)
+{
+	struct cyclic_info *cyclic;
+
+	if (!gd->cyclic->cyclic_ready) {
+		pr_debug("Cyclic IF not ready yet\n");
+		return NULL;
+	}
+
+	cyclic = calloc(1, sizeof(struct cyclic_info));
+	if (!cyclic) {
+		pr_debug("Memory allocation error\n");
+		return NULL;
+	}
+
+	/* Store values in struct */
+	cyclic->func = func;
+	cyclic->ctx = ctx;
+	cyclic->name = strdup(name);
+	cyclic->delay_us = delay_us;
+	cyclic->start_time_us = timer_get_us();
+	list_add_tail(&cyclic->list, &gd->cyclic->cyclic_list);
+
+	return cyclic;
+}
+
+int cyclic_unregister(struct cyclic_info *cyclic)
+{
+	list_del(&cyclic->list);
+	free(cyclic);
+
+	return 0;
+}
+
+void cyclic_run(void)
+{
+	struct cyclic_info *cyclic, *tmp;
+	uint64_t now, cpu_time;
+
+	/* Prevent recursion */
+	if (gd->cyclic->cyclic_running)
+		return;
+
+	gd->cyclic->cyclic_running = true;
+	list_for_each_entry_safe(cyclic, tmp, &gd->cyclic->cyclic_list, list) {
+		/*
+		 * Check if this cyclic function needs to get called, e.g.
+		 * do not call the cyclic func too often
+		 */
+		now = timer_get_us();
+		if (time_after_eq64(now, cyclic->next_call)) {
+			/* Call cyclic function and account it's cpu-time */
+			cyclic->next_call = now + cyclic->delay_us;
+			cyclic->func(cyclic->ctx);
+			cyclic->run_cnt++;
+			cpu_time = timer_get_us() - now;
+			cyclic->cpu_time_us += cpu_time;
+
+			/* Check if cpu-time exceeds max allowed time */
+			if (cpu_time > CONFIG_CYCLIC_MAX_CPU_TIME_US) {
+				pr_err("cyclic function %s took too long: %lldus vs %dus max, disabling\n",
+				       cyclic->name, cpu_time,
+				       CONFIG_CYCLIC_MAX_CPU_TIME_US);
+
+				/* Unregister this cyclic function */
+				cyclic_unregister(cyclic);
+			}
+		}
+	}
+	gd->cyclic->cyclic_running = false;
+}
+
+int cyclic_uninit(void)
+{
+	struct cyclic_info *cyclic, *tmp;
+
+	list_for_each_entry_safe(cyclic, tmp, &gd->cyclic->cyclic_list, list)
+		cyclic_unregister(cyclic);
+	gd->cyclic->cyclic_ready = false;
+
+	return 0;
+}
+
+int cyclic_init(void)
+{
+	int size = sizeof(struct cyclic_drv);
+
+	gd->cyclic = (struct cyclic_drv *)malloc(size);
+	if (!gd->cyclic)
+		return -ENOMEM;
+
+	memset(gd->cyclic, '\0', size);
+	INIT_LIST_HEAD(&gd->cyclic->cyclic_list);
+	gd->cyclic->cyclic_ready = true;
+
+	return 0;
+}
diff --git a/configs/octeon_nic23_defconfig b/configs/octeon_nic23_defconfig
index 95e98c1..098831e 100644
--- a/configs/octeon_nic23_defconfig
+++ b/configs/octeon_nic23_defconfig
@@ -20,6 +20,8 @@
 CONFIG_OF_BOARD_FIXUP=y
 CONFIG_SYS_CONSOLE_ENV_OVERWRITE=y
 # CONFIG_SYS_DEVICE_NULLDEV is not set
+CONFIG_CYCLIC=y
+CONFIG_CYCLIC_MAX_CPU_TIME_US=5000
 CONFIG_ARCH_MISC_INIT=y
 CONFIG_BOARD_EARLY_INIT_F=y
 CONFIG_BOARD_LATE_INIT=y
@@ -41,6 +43,7 @@
 CONFIG_CMD_EXT4=y
 CONFIG_CMD_FAT=y
 CONFIG_CMD_FS_GENERIC=y
+CONFIG_CMD_CYCLIC=y
 CONFIG_EFI_PARTITION=y
 CONFIG_ENV_IS_IN_SPI_FLASH=y
 CONFIG_TFTP_TSIZE=y
diff --git a/doc/develop/cyclic.rst b/doc/develop/cyclic.rst
new file mode 100644
index 0000000..43bedac
--- /dev/null
+++ b/doc/develop/cyclic.rst
@@ -0,0 +1,50 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+Cyclic functions
+================
+
+The cyclic function execution infrastruture provides a way to periodically
+execute code, e.g. every 100ms. Examples for such functions might be LED
+blinking etc. The functions that are hooked into this cyclic list should
+be small timewise as otherwise the execution of the other code that relies
+on a high frequent polling (e.g. UART rx char ready check) might be
+delayed too much. To detect cyclic functions with a too long execution
+time, the Kconfig option `CONFIG_CYCLIC_MAX_CPU_TIME_US` is introduced,
+which configures the max allowed time for such a cyclic function. If it's
+execution time exceeds this time, this cyclic function will get removed
+from the cyclic list.
+
+Registering a cyclic function
+-----------------------------
+
+To register a cyclic function, use something like this::
+
+    static void cyclic_demo(void *ctx)
+    {
+        /* Just a small dummy delay here */
+        udelay(10);
+    }
+    
+    int board_init(void)
+    {
+        struct cyclic_info *cyclic;
+        
+        /* Register demo cyclic function */
+        cyclic = cyclic_register(cyclic_demo, 10 * 1000, "cyclic_demo", NULL);
+        if (!cyclic)
+        printf("Registering of cyclic_demo failed\n");
+        
+        return 0;
+    }
+
+This will register the function `cyclic_demo()` to be periodically
+executed all 10ms.
+
+How is this cyclic functionality integrated /  executed?
+--------------------------------------------------------
+
+The cyclic infrastructure integrates the main function responsible for
+calling all registered cyclic functions cyclic_run() into the common
+WATCHDOG_RESET macro. This guarantees that cyclic_run() is executed
+very often, which is necessary for the cyclic functions to get scheduled
+and executed at their configured periods.
diff --git a/doc/develop/index.rst b/doc/develop/index.rst
index f7ee09d..ce6b38e 100644
--- a/doc/develop/index.rst
+++ b/doc/develop/index.rst
@@ -27,6 +27,7 @@
    ci_testing
    commands
    config_binding
+   cyclic
    devicetree/index
    distro
    driver-model/index
diff --git a/doc/usage/cmd/cyclic.rst b/doc/usage/cmd/cyclic.rst
new file mode 100644
index 0000000..3085cc7
--- /dev/null
+++ b/doc/usage/cmd/cyclic.rst
@@ -0,0 +1,45 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+cyclic command
+==============
+
+Synopsis
+--------
+
+::
+
+    cyclic list
+
+Description
+-----------
+
+The cyclic list command provides a list of the currently registered
+cyclic functions.
+
+This shows the following information:
+
+Function
+    Function name
+
+cpu-time
+    Total time spent in this cyclic function.
+
+Frequency
+    Frequency of execution of this function, e.g. 100 times/s for a
+    pediod of 10ms.
+
+
+See :doc:`../../develop/cyclic` for more information on cyclic functions.
+
+Example
+-------
+
+::
+
+    => cyclic list
+    function: cyclic_demo, cpu-time: 52906 us, frequency: 99.20 times/s
+
+Configuration
+-------------
+
+The cyclic command is only available if CONFIG_CMD_CYCLIC=y.
diff --git a/doc/usage/index.rst b/doc/usage/index.rst
index b36305c..73966c6 100644
--- a/doc/usage/index.rst
+++ b/doc/usage/index.rst
@@ -33,6 +33,7 @@
    cmd/bootz
    cmd/cbsysinfo
    cmd/conitrace
+   cmd/cyclic
    cmd/dm
    cmd/echo
    cmd/eficonfig
diff --git a/fs/cramfs/uncompress.c b/fs/cramfs/uncompress.c
index f431cc4..38e10e2 100644
--- a/fs/cramfs/uncompress.c
+++ b/fs/cramfs/uncompress.c
@@ -62,7 +62,7 @@
 	stream.avail_in = 0;
 
 #if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)
-	stream.outcb = (cb_func) WATCHDOG_RESET;
+	stream.outcb = (cb_func)watchdog_reset_func;
 #else
 	stream.outcb = Z_NULL;
 #endif /* CONFIG_HW_WATCHDOG */
diff --git a/include/asm-generic/global_data.h b/include/asm-generic/global_data.h
index 805a6fd..9006c76 100644
--- a/include/asm-generic/global_data.h
+++ b/include/asm-generic/global_data.h
@@ -20,6 +20,7 @@
  */
 
 #ifndef __ASSEMBLY__
+#include <cyclic.h>
 #include <event_internal.h>
 #include <fdtdec.h>
 #include <membuff.h>
@@ -474,6 +475,12 @@
 	 */
 	struct event_state event_state;
 #endif
+#ifdef CONFIG_CYCLIC
+	/**
+	 * @cyclic: cyclic driver data
+	 */
+	struct cyclic_drv *cyclic;
+#endif
 	/**
 	 * @dmtag_list: List of DM tags
 	 */
diff --git a/include/cyclic.h b/include/cyclic.h
new file mode 100644
index 0000000..2390223
--- /dev/null
+++ b/include/cyclic.h
@@ -0,0 +1,138 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * A general-purpose cyclic execution infrastructure, to allow "small"
+ * (run-time wise) functions to be executed at a specified frequency.
+ * Things like LED blinking or watchdog triggering are examples for such
+ * tasks.
+ *
+ * Copyright (C) 2022 Stefan Roese <sr@denx.de>
+ */
+
+#ifndef __cyclic_h
+#define __cyclic_h
+
+#include <linux/list.h>
+#include <asm/types.h>
+
+/**
+ * struct cyclic_drv - Cyclic driver internal data
+ *
+ * @cyclic_list: Cylic list node
+ * @cyclic_ready: Flag if cyclic infrastructure is ready
+ * @cyclic_running: Flag if cyclic infrastructure is running
+ */
+struct cyclic_drv {
+	struct list_head cyclic_list;
+	bool cyclic_ready;
+	bool cyclic_running;
+};
+
+/**
+ * struct cyclic_info - Information about cyclic execution function
+ *
+ * @func: Function to call periodically
+ * @ctx: Context pointer to get passed to this function
+ * @name: Name of the cyclic function, e.g. shown in the commands
+ * @delay_ns: Delay is ns after which this function shall get executed
+ * @start_time_us: Start time in us, when this function started its execution
+ * @cpu_time_us: Total CPU time of this function
+ * @run_cnt: Counter of executions occurances
+ * @next_call: Next time in us, when the function shall be executed again
+ * @list: List node
+ */
+struct cyclic_info {
+	void (*func)(void *ctx);
+	void *ctx;
+	char *name;
+	uint64_t delay_us;
+	uint64_t start_time_us;
+	uint64_t cpu_time_us;
+	uint64_t run_cnt;
+	uint64_t next_call;
+	struct list_head list;
+};
+
+/** Function type for cyclic functions */
+typedef void (*cyclic_func_t)(void *ctx);
+
+#if defined(CONFIG_CYCLIC)
+/**
+ * cyclic_register - Register a new cyclic function
+ *
+ * @func: Function to call periodically
+ * @delay_us: Delay is us after which this function shall get executed
+ * @name: Cyclic function name/id
+ * @ctx: Context to pass to the function
+ * @return: pointer to cyclic_struct if OK, NULL on error
+ */
+struct cyclic_info *cyclic_register(cyclic_func_t func, uint64_t delay_us,
+				    const char *name, void *ctx);
+
+/**
+ * cyclic_unregister - Unregister a cyclic function
+ *
+ * @cyclic: Pointer to cyclic_struct of the function that shall be removed
+ * @return: 0 if OK, -ve on error
+ */
+int cyclic_unregister(struct cyclic_info *cyclic);
+
+/**
+ * cyclic_init() - Set up cyclic functions
+ *
+ * Init a list of cyclic functions, so that these can be added as needed
+ */
+int cyclic_init(void);
+
+/**
+ * cyclic_uninit() - Clean up cyclic functions
+ *
+ * This removes all cyclic functions
+ */
+int cyclic_uninit(void);
+
+/**
+ * cyclic_get_list() - Get cyclic list pointer
+ *
+ * Return the cyclic list pointer
+ *
+ * @return: pointer to cyclic_list
+ */
+struct list_head *cyclic_get_list(void);
+
+/**
+ * cyclic_run() - Interate over all registered cyclic functions
+ *
+ * Interate over all registered cyclic functions and if the it's function
+ * needs to be executed, then call into these registered functions.
+ */
+void cyclic_run(void);
+#else
+static inline struct cyclic_info *cyclic_register(cyclic_func_t func,
+						  uint64_t delay_us,
+						  const char *name,
+						  void *ctx)
+{
+	return NULL;
+}
+
+static inline int cyclic_unregister(struct cyclic_info *cyclic)
+{
+	return 0;
+}
+
+static inline void cyclic_run(void)
+{
+}
+
+static inline int cyclic_init(void)
+{
+	return 0;
+}
+
+static inline int cyclic_uninit(void)
+{
+	return 0;
+}
+#endif
+
+#endif
diff --git a/include/image.h b/include/image.h
index e4c6a50..a148073 100644
--- a/include/image.h
+++ b/include/image.h
@@ -360,6 +360,12 @@
 #endif
 } bootm_headers_t;
 
+#ifdef CONFIG_LMB
+#define images_lmb(_images)	(&(_images)->lmb)
+#else
+#define images_lmb(_images)	NULL
+#endif
+
 extern bootm_headers_t images;
 
 /*
diff --git a/include/time.h b/include/time.h
index 9deb2cf..3b2ba09 100644
--- a/include/time.h
+++ b/include/time.h
@@ -83,6 +83,25 @@
 	(time_after_eq(a,b) && \
 	 time_before(a,c))
 
+/* Same as above, but does so with platform independent 64bit types.
+ * These must be used when utilizing jiffies_64 (i.e. return value of
+ * get_jiffies_64() */
+#define time_after64(a,b)	\
+	(typecheck(__u64, a) &&	\
+	 typecheck(__u64, b) && \
+	 ((__s64)((b) - (a)) < 0))
+#define time_before64(a,b)	time_after64(b,a)
+
+#define time_after_eq64(a,b)	\
+	(typecheck(__u64, a) && \
+	 typecheck(__u64, b) && \
+	 ((__s64)((a) - (b)) >= 0))
+#define time_before_eq64(a,b)	time_after_eq64(b,a)
+
+#define time_in_range64(a, b, c) \
+	(time_after_eq64(a, b) && \
+	 time_before_eq64(a, c))
+
 /**
  * usec2ticks() - Convert microseconds to internal ticks
  *
diff --git a/include/watchdog.h b/include/watchdog.h
index 813cc8f..0a9777e 100644
--- a/include/watchdog.h
+++ b/include/watchdog.h
@@ -11,6 +11,8 @@
 #define _WATCHDOG_H_
 
 #if !defined(__ASSEMBLY__)
+#include <cyclic.h>
+
 /*
  * Reset the watchdog timer, always returns 0
  *
@@ -60,11 +62,16 @@
 			/* Don't require the watchdog to be enabled in SPL */
 			#if defined(CONFIG_SPL_BUILD) &&		\
 				!defined(CONFIG_SPL_WATCHDOG)
-				#define WATCHDOG_RESET() {}
+				#define WATCHDOG_RESET() { \
+					cyclic_run(); \
+				}
 			#else
 				extern void watchdog_reset(void);
 
-				#define WATCHDOG_RESET watchdog_reset
+				#define WATCHDOG_RESET() { \
+					watchdog_reset(); \
+					cyclic_run(); \
+				}
 			#endif
 		#endif
 	#else
@@ -74,11 +81,21 @@
 		#if defined(__ASSEMBLY__)
 			#define WATCHDOG_RESET /*XXX DO_NOT_DEL_THIS_COMMENT*/
 		#else
-			#define WATCHDOG_RESET() {}
+			#define WATCHDOG_RESET() { \
+				cyclic_run(); \
+			}
 		#endif /* __ASSEMBLY__ */
 	#endif /* CONFIG_WATCHDOG && !__ASSEMBLY__ */
 #endif /* CONFIG_HW_WATCHDOG */
 
+#if !defined(__ASSEMBLY__)
+/* Currently only needed for fs/cramfs/uncompress.c */
+static inline void watchdog_reset_func(void)
+{
+	WATCHDOG_RESET();
+}
+#endif
+
 /*
  * Prototypes from $(CPU)/cpu.c.
  */
diff --git a/test/common/Makefile b/test/common/Makefile
index 9087788..cc918f6 100644
--- a/test/common/Makefile
+++ b/test/common/Makefile
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0+
 obj-y += cmd_ut_common.o
 obj-$(CONFIG_AUTOBOOT) += test_autoboot.o
+obj-$(CONFIG_CYCLIC) += cyclic.o
 obj-$(CONFIG_EVENT) += event.o
diff --git a/test/common/cyclic.c b/test/common/cyclic.c
new file mode 100644
index 0000000..a5c4e78
--- /dev/null
+++ b/test/common/cyclic.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2022 Stefan Roese <sr@denx.de>
+ */
+
+#include <common.h>
+#include <cyclic.h>
+#include <dm.h>
+#include <test/common.h>
+#include <test/test.h>
+#include <test/ut.h>
+#include <watchdog.h>
+#include <linux/delay.h>
+
+/* Test that cyclic function is called */
+static bool cyclic_active = false;
+
+static void cyclic_test(void *ctx)
+{
+	cyclic_active = true;
+}
+
+static int dm_test_cyclic_running(struct unit_test_state *uts)
+{
+	cyclic_active = false;
+	ut_assertnonnull(cyclic_register(cyclic_test, 10 * 1000, "cyclic_demo",
+					 NULL));
+
+	/* Execute all registered cyclic functions */
+	WATCHDOG_RESET();
+	ut_asserteq(true, cyclic_active);
+
+	return 0;
+}
+COMMON_TEST(dm_test_cyclic_running, 0);
diff --git a/test/test-main.c b/test/test-main.c
index 4f1c54b..ae34002 100644
--- a/test/test-main.c
+++ b/test/test-main.c
@@ -6,6 +6,7 @@
 
 #include <common.h>
 #include <console.h>
+#include <cyclic.h>
 #include <dm.h>
 #include <event.h>
 #include <dm/root.h>
@@ -220,6 +221,7 @@
 static int test_pre_run(struct unit_test_state *uts, struct unit_test *test)
 {
 	ut_assertok(event_init());
+	ut_assertok(cyclic_init());
 
 	if (test->flags & UT_TESTF_DM)
 		ut_assertok(dm_test_pre_run(uts));
@@ -265,6 +267,7 @@
 	ut_unsilence_console(uts);
 	if (test->flags & UT_TESTF_DM)
 		ut_assertok(dm_test_post_run(uts));
+	ut_assertok(cyclic_uninit());
 	ut_assertok(event_uninit());
 
 	return 0;